Spring Security入门01-22 登录验证功能
课程链接:SpringSecurity框架教程
开始时间:2022-07-17
快速入门
搭建一个Spring Boot项目
添加基础依赖和创建启动类和controller
controller
@RestController
public class HelloController {@RequestMapping("/hello")public String hello() {return "hello";}
}
启动类
package com.bupt.security_test;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SecurityTestApplication {public static void main(String[] args) {SpringApplication.run(SecurityTestApplication.class, args);}}
此时访问页面localhost:8080/hello
可以看到显示hello这一个单词
引入Security的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
此时我们再去访问
localhost:8080/hello
会自动跳转到security带的登录界面
登录名默认是user
密码会在控制台打印给你
如果输入错误的账户密码
输入正确
默认有一个退出的接口logout
认证
登录校验流程图
Spring Security流程
本质是一个过滤器链
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。
ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。 发现异常并重定向
FilterSecurityInterceptor:负责权限校验的过滤器。(他实现了过滤器接口)
没想到还有重温之前学拦截器的部分,参考博客
这里只是选了三个典型的,还有其他过滤器,暂不研究
我们可以查看有哪些过滤器
认证流程详解
区分一下概念
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
AuthenticationManager接口:定义了认证Authentication的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
第一步:提交用户名和密码 传入到UsernamePasswordAuthenticationFilter,这个过滤器把用户名和密码封装为一个Authentication对象,此时还没有权限信息
第二步 调用authenticate方法进行认证,链条走到AuthenticationManager,但他也没完,继续调用DaoAuthenticationProvider的authenticate方法进行认证
第三步 即使已经经过了三个过滤器,还是没有认证,需要继续调用loadUserByUsername方法查询用户,走到第四个链条处了
第四步 根据用户名去查询用户及该用户对应的权限信息,InMemoryUserDetailsManager是在内存中查找的,并把对应用户信息添加上权限信息封装成UserDetails对象进行返回
第五步 UserDetails返回到Provider处,判断PasswordEncoder对比UserDetails中的密码和Authentication的密码是否正确,如果正确就把UserDetails中的权限信息设置到Authentication对象中
第六步 返回Authentication对象到第一个过滤器处,如果成功返回,就使用SecurityContextHolder.getContext().setAuthentication方法村吃该对象,其他过滤器可以通过SecurityContextHolder来获取当前用户信息
我们想想,在第四步中,查询的信息是在内存中查的,而我们需要从数据库查,那就得自己来实现这个接口
之后再请求
那么我们经过JWT拿到UserID后,怎么获取完整信息呢?再去数据库一条条查?会增大IO开销
因此,Redis闪亮登场。
那JWT要去Redis里面查,那总得Redis里面有东西吧,什么时候放呢?就在我们登录接口那里,如果认证通过,生成一个JWT的同时,再把UserID:用户信息存入Redis里面即可
解决问题
思路分析
登录
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService
在这个实现类中去查询数据库校验:
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
准备工作,添加所需要的Maven依赖以及相应的工具类
功能实现
数据库校验用户
我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。
建表并引入MybatisPuls和mysql驱动的依赖
CREATE TABLE `sys_user` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',`nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',`password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',`status` CHAR(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',`email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱',`phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手机号',`sex` CHAR(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',`avatar` VARCHAR(128) DEFAULT NULL COMMENT '头像',`user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',`create_by` BIGINT(20) DEFAULT NULL COMMENT '创建人的用户id',`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',`update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人',`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',`del_flag` INT(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'
配置数据库信息
配置mapper
package com.bupt.security_test.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bupt.security_test.domain.User;public interface UserMapper extends BaseMapper<User> {}
在主启动类上扫描mapper
@MapperScan("com.bupt.security_test.mapper")
public class SecurityTestApplication
测试一下
@SpringBootTest
public class SecurityTestApplicationTests {@Autowiredprivate UserMapper userMapper;@Testpublic void testUserMapper() {List<User> users = userMapper.selectList(null);System.out.println(users);}
}
读取到了数据库数据,说明MyBatisPlus没问题
那我们就要尝试链接自己的数据库的账号密码了
创建一个类实现UserDetailsService接口,重写其中的方法。
这里的==@service注解不能忘记==
package com.bupt.security_test.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bupt.security_test.domain.LoginUser;
import com.bupt.security_test.domain.User;
import com.bupt.security_test.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.Objects;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserName, username);User user = userMapper.selectOne(queryWrapper);//没查到用户就抛异常if (Objects.isNull(user)) {throw new RuntimeException("用户不存在");}//TODO 查询对应权限return new LoginUser(user);}
}
返回的实体类LoginUser我们也定义一下,因为我们的User本身和UserService没关系,需要借助LoginUser包装一下
注意后面几个方法虽然没实现,但返回改为了true而不是默认的false
package com.bupt.security_test.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {private User user;//获取访问权限信息@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}//是否没过期@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}//@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
测试要求输入账号密码
现阶段要把password密码明文前面加上{noop}
不然识别不出来
这个括号里面是说明该密码采取什么方式进行的加密,{noop}表示没有加密
为什么呢,我们再看看上面的图
实际项目中我们不会把密码明文存储在数据库中。
默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。
我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。
我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。
我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。
配置一下SecurityConfig
package com.bupt.security_test.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}
写一个测试类看看
@Testpublic void TestBCryptPasswordEncoder() {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode1 = bCryptPasswordEncoder.encode("1234");String encode2 = bCryptPasswordEncoder.encode("1234");System.out.println(encode1);System.out.println(encode2);}
我们打印输出,会发现每次encode的结果都不一致
$2a$10$NMAeABItflg2UyrWNuOwvu0hFhEqGIk.zyx0RUJbeAm6jrjQq54oi
$2a$10$p5J1O.5THmLqv9ATSv/juep3QxhsUOyGTh/5bL51.8kcwKMNNYlle
我们存数据库的密码不是明文,而是encode后的结果
但不管怎样,用谁加密,对应匹配他还是认识
再看一看
@Testpublic void TestBCryptPasswordEncoder() {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode1 = bCryptPasswordEncoder.encode("1234");String encode2 = bCryptPasswordEncoder.encode("1234");System.out.println("encode1输出" + encode1);System.out.println("encode1判断是否能匹配rawPassword" + bCryptPasswordEncoder.matches("1234", encode1));System.out.println("encode2输出" + encode2);System.out.println("encode2判断是否能匹配rawPassword" + bCryptPasswordEncoder.matches("1234", encode2));}
输出
encode1输出$2a$10$PJNq2HpUoHdj52zp3dPdNO9wKTpuGJHYeF.nX.NyDjt8jWUBqRM46
encode1判断是否能匹配rawPasswordtrue
encode2输出$2a$10$WAwFkHZ8xWoR3e5D9zkWgefxIrrRsZv8B093x9FYA7I/jCqMw8mvi
encode2判断是否能匹配rawPasswordtrue
这里为什么不同的编码都能匹配上呢,知识超纲了,这好像是什么 盐 值
配置好之后,我们直接输入用户密码就不行了,要更新数据库的密码为加密后的密码才行
JWT工具类
package com.bupt.security_test.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;/*** JWT工具类*/
public class JwtUtil {//有效期为public static final Long JWT_TTL = 60 * 60 * 1000L;// 60 * 60 *1000 一个小时//设置秘钥明文public static final String JWT_KEY = "bupt";public static String getUUID() {String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}/*** 生成jtw** @param subject token中要存放的数据(json格式)* @return*/public static String createJWT(String subject) {JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间return builder.compact();}/*** 生成jwt** @param subject token中要存放的数据(json格式)* @param ttlMillis token超时时间* @return*/public static String createJWT(String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间return builder.compact();}private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;SecretKey secretKey = generalKey();long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if (ttlMillis == null) {ttlMillis = JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);return Jwts.builder().setId(uuid) //唯一的ID.setSubject(subject) // 主题 可以是JSON数据.setIssuer("jdh") // 签发者.setIssuedAt(now) // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);}/*** 创建token** @param id* @param subject* @param ttlMillis* @return*/public static String createJWT(String id, String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间return builder.compact();}public static void main(String[] args) throws Exception {String jwt = createJWT("2123");System.out.println(jwt);//base64解码Claims claims = parseJWT(jwt);System.out.println(claims.getSubject());//String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";//Claims claims = parseJWT(token);//System.out.println(claims);}/*** 生成加密后的秘钥 secretKey** @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}
执行主方法得到
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJiMGQxYmNjODI0NzI0ODljYjZlMDdiMGIxNDkyMzhkZiIsInN1YiI6IjIxMjMiLCJpc3MiOiJqZGgiLCJpYXQiOjE2NTgwNDM0ODQsImV4cCI6MTY1ODA0NzA4NH0.R4WV-QID5r2gACp7qXYV_fXy13VFAV0cjBzUfgFO-v8
2123
登录接口
自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。
在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。
认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。
首先说如何放行
SecurityConfig重写方法
//用来放行,总不能登录界面都要让你认证吧@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();}
然后正常的controller service serviceimpl走一遍
package com.bupt.security_test.controller;import com.bupt.security_test.domain.ResponseResult;
import com.bupt.security_test.domain.User;
import com.bupt.security_test.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class LoginController {@Autowiredprivate LoginService loginService;@PostMapping("/user/login")//RequestBody用来拿JSON传过来的用户名和密码public ResponseResult login(@RequestBody User user) {//登录return loginService.login(user);}
}
public interface LoginService {ResponseResult login(User user);
}
@Service
public class LoginServiceImpl implements LoginService {//这个类在SecurityConfig里面实现的@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisCache redisCache;@Overridepublic ResponseResult login(User user) {//AuthenticationManager authenticate进行用户认证//用户名和密码先封装,而Authentication是接口,我们需要找一个他的实现类//在接口名上 ctrl+alt+鼠标左键,可以看其常用实现类UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());//通过authenticationManager来实现认证,会调用UserDetailsServiceImplAuthentication authenticate = authenticationManager.authenticate(authenticationToken);//如果认证没通过,给出对应的提示if (Objects.isNull(authenticate)) {throw new RuntimeException("登录失败");}//如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回LoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userid = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userid);Map<String, String> map = new HashMap<>();map.put("token", jwt);//把完整的用户信息存入redis userid作为keyredisCache.setCacheObject("login:" + userid, loginUser);return new ResponseResult(200, "登录成功", map);}
}
而我们需要先暴露AuthenticationManager
因此要在SecurityConfig添加
//重写方法,用来暴露AuthenticationManager@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
使用postman进行debug
debug看,
//通过authenticationManager来实现认证,会调用UserDetailsServiceImplAuthentication authenticate = authenticationManager.authenticate(authenticationToken);
拿到的 authenticate包含的principal下有user信息
集成redis后,我们再来看看测试结果
我们把这个token拿回工具类解析
Claims claim = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwNWNkN2E1ZDhlYzg0Y2NiYWU5NmM3ZDliMjUyOWViOSIsInN1YiI6IjEiLCJpc3MiOiJqZGgiLCJpYXQiOjE2NTgwNDgyMjcsImV4cCI6MTY1ODA1MTgyN30.8yp6VBEHL2aAB5dTlsG13lsRg_XNlNmVNZ88zumhT4c");System.out.println(claim.getSubject());
得到的输出为1,即该用户的id为1
认证过滤器
校验:
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
过滤器代码
package com.bupt.security_test.filter;import com.bupt.security_test.domain.LoginUser;
import com.bupt.security_test.utils.JwtUtil;
import com.bupt.security_test.utils.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;//认证过滤器,那这个过滤器需要在SecurityConfig里面配置他出现的位置
//他要出现在UsernamePasswordAuthenticationFilter之前
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取token,从请求头里面拿String token = request.getHeader("token");//没有token,直接放行//放行后会去执行后面的过滤器//走完后面的过滤器,响应回来的时候还会走一遍过滤器链//如果没有return,回来的时候还会执行一次,就会报错,因为根本没有token//登录接口也会从这里过,但因为没有token就被放行了if (!StringUtils.hasText(token)) {filterChain.doFilter(request, response);return;}String userid;//解析tokentry {Claims claims = JwtUtil.parseJWT(token);userid = claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new RuntimeException("token非法");}//从redis里面获取用户信息String redisKey = "login:" + userid;LoginUser loginUser = redisCache.getCacheObject(redisKey);//存入SecurityContextHolderif (Objects.isNull(loginUser)) {throw new RuntimeException("用户未登录");}//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}
配置过滤器位置
SecurityConfig
//添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
测试一下,发送post请求
debug发现在
if (!StringUtils.hasText(token)) {filterChain.doFilter(request, response);return;}
直接就放行了
然后一直走,完成登录后就返回token
此时我们再来测试一下hello接口
被拦截下来了,需要补充token信息
这样就能访问了
退出登录
清空redis SecurityContextHolder
//退出登录@RequestMapping("/user/logout")public ResponseResult logout() {return loginService.logout();}
实现类
//退出登录@Overridepublic ResponseResult logout() {//获取SecurityContextHolder中的用户idUsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();Long userid = loginUser.getUser().getId();//用户如果是未登录状态发起退出请求会被拦截下来,根本到不了这个方法//删除redis中的值redisCache.deleteObject("login:" + userid);return new ResponseResult(200, "注销成功");}
此时我们再使用原来的token访问就不行
结束时间:2022-07-17
Spring Security入门01-22 登录验证功能相关推荐
- 【Spring Security入门】06-QQ登录实现
准备工作 1.在 QQ互联 申请成为开发者,并创建应用,得到APP ID 和 APP Key. 2.了解QQ登录时的 网站应用接入流程.(必须看完看懂) 为了方便各位测试,直接把我自己申请的贡献出来: ...
- Spring Security 入门(3-11)Spring Security 的使用-自定义登录验证和回调地址
配置文件 security-ns.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmln ...
- Spring Security入门到实践(一)HTTP Basic在Spring Security中的应用原理浅析
一.Spring Security简介 打开Spring Security的官网,从其首页的预览上就可以看见如下文字: Spring Security is a powerful and highly ...
- Spring Security入门基础
Spring Security入门基础 文章目录 Spring Security入门基础 一,Spring Security的使用 1.1 基本术语 1.2 基本使用 1.2.1 引入依赖 1.2.2 ...
- 5.Spring Security 短信验证码登录
Spring Security 短信验证码登录 在 Spring Security 添加图形验证码一节中,我们已经实现了基于 Spring Boot + Spring Security 的账号密码登录 ...
- Spring Security 入门(五):在 Spring-Boot中的应用
前言 本文作为入门级的DEMO,完全按照官网实例演示: 项目目录结构 Maven 依赖 <parent> <groupId>org.springframework.bo ...
- Spring Security 入门(四):自定义-Filter
前文导读 - Spring Security入门(一):登录与退出 - Spring Security入门(二):基于数据库验证 - Spring Security入门(三):密码加密 本文解决问题 ...
- Spring Security入门(三):密码加密
前文导读 - Spring Security入门(一):登录与退出 - Spring Security入门(二):基于数据库验证 Github 地址 https://github.com/ChinaS ...
- spring 3.x 学习笔记_spring mvc、spring jdbc 实现网站的登录注册功能
使用spring mvc.spring jdbc 实现网站的登录注册功能 1. 据业务模型 创建model 一般实现序列化 2. 用spring 注解(@Repositor ...
- 关于Spring Security框架 关于单点登录sso
1.Spring Security的作用 Spring Security主要解决了认证和授权相关的问题. 认证(Authenticate):验证用户身份,即登录. 授权(Authorize):允许用户 ...
最新文章
- python处理excel字典-使用Python代码处理Excel
- Android中使用ViewStub提高布局性能
- Visual Studio 2019没有Setup安装项目(Microsoft Visual Studio Installer Projects)的官方解决方案
- 转:论文写作与投稿的一点经验,加两封催稿信
- Apache2 httpd.conf配置文件中文版详解
- GitHub控件之BadgeView(数字提醒)
- 看图说cnblogs-强大的SEO功能【有实例】
- MYSQL支持事务吗?
- 【评分】个人作业——软件工程实践总结作业
- 羊皮卷的故事-第十七章-羊皮卷之十
- PetaLinux 添加启动后自动执行脚本
- Google SketchUp,一款很有趣的软件,可以创建你所想要的任何3D模型
- wordpress主题_20个美丽的Flat WordPress主题,为您的网站增光添彩
- JS---------------网页版的消灭星星
- java jtextarea 事件_JTextArea的事件处理2
- U盘文件被病毒破坏的常见迹象和数据恢复方法
- Java爬虫html解析-Jsoup(绿盟极光报告)
- 怎样去学习——思维导图
- 建立时间和保持时间的计算
- 模拟MIC和数字MIC
热门文章
- 30分钟学会js新特性
- Web身份验证(WebAuthn)
- Linux——vi/vim文本编辑器、用户管理、关机重启的相关命令
- es6字符串添加html标签,JavaScript_详解JavaScript ES6中的模板字符串,在 ES6 中引入了一种新的字符 - phpStudy...
- XJTUSE专业课与实验指南(已经开源)
- js简易版歌单播放,可切换下一首
- arcengine双击属性表内的一行,定位到该要素,并添加到选择集
- 四十七、Fluent近壁面处理
- 非线性优化汇总——Matlab优化工具箱(持续更新中)
- SI522A/SI523带低功耗自动寻卡功能的13.56MHz非接触式读写器芯片