**
SpringSecurity
登录认证和请求过滤器以及安全配置详解说明**
环境
系统环境:win10
Maven环境:apache-maven-3.8.6
JDK版本:1.8
SpringBoot版本:2.7.8
根据用户名密码登录
根据用户名和密码登录,登录成功后返回
Token
数据,将
token放到请求头中
,每次请求后台携带
token
数据
认证成功,返回请求数据
携带token请求后台
,后台认证成功,过滤器放行,返回请求数据
认证失败,SpringSecurity拦截请求
携带token请求后台,后台认证失败,请求被拦截
数据表结构
CREATETABLE`sys_user`(`id`BIGINT(20)NOTNULLAUTO_INCREMENTCOMMENT'主键',`user_name`VARCHAR(64)NOTNULLDEFAULT'NULL'COMMENT'用户名',`nick_name`VARCHAR(64)NOTNULLDEFAULT'NULL'COMMENT'昵称',`password`VARCHAR(64)NOTNULLDEFAULT'NULL'COMMENT'密码',`status`CHAR(1)DEFAULT'0'COMMENT'账号状态(0正常 1停用)',`email`VARCHAR(64)DEFAULTNULLCOMMENT'邮箱',`phonenumber`VARCHAR(32)DEFAULTNULLCOMMENT'手机号',`sex`CHAR(1)DEFAULTNULLCOMMENT'用户性别(0男,1女,2未知)',`avatar`VARCHAR(128)DEFAULTNULLCOMMENT'头像',`user_type`CHAR(1)NOTNULLDEFAULT'1'COMMENT'用户类型(0管理员,1普通用户)',`create_by`BIGINT(20)DEFAULTNULLCOMMENT'创建人的用户id',`create_time`DATETIMEDEFAULTNULLCOMMENT'创建时间',`update_by`BIGINT(20)DEFAULTNULLCOMMENT'更新人',`update_time`DATETIMEDEFAULTNULLCOMMENT'更新时间',`del_flag`INT(11)DEFAULT'0'COMMENT'删除标志(0代表未删除,1代表已删除)',PRIMARYKEY(`id`))ENGINE=INNODBAUTO_INCREMENT=2DEFAULTCHARSET=utf8mb4 COMMENT='用户表'
下面是本次
Demo
的项目代码和说明
项目环境依赖
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.8</version><relativePath/></parent><groupId>cn.molu.security.jwt</groupId><artifactId>SpringSecurity-JWT</artifactId><version>0.0.1-SNAPSHOT</version><name>SpringSecurity-JWT</name><description>SpringSecurity-JWT</description><properties><java.version>1.8</java.version></properties><dependencies><!--SpringSecurity安全框架--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--启用SpringBoot对Web的支持--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--热部署插件--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><!--Lombok实体类简化组件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--lang3对象工具包--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!--hutool工具包,数据加解密,对象判空转换等--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.10</version></dependency><!-- UA解析工具(从request中解析出访问设备信息) --><dependency><groupId>eu.bitwalker</groupId><artifactId>UserAgentUtils</artifactId><version>1.21</version></dependency><!--生成token依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- MySQL数据连接驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><!--MyBatis-Plus操作数据库--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
项目启动入口
使用
MyBatis-Plus
操作数据库,配置扫描
mapper
所在的包
@SpringBootApplication@MapperScan("cn.molu.security.jwt.mapper")publicclassSpringSecurityJwtApplication{publicstaticvoidmain(String[] args){SpringApplication.run(SpringSecurityJwtApplication.class, args);}}
项目配置文件
MySQL地址、项目访问端口、token有效期
spring:# 数据库链接配置datasource:url: jdbc:mysql://127.0.0.1:3306/security?characterEncoding=utf8&serverTimezone=UTCusername: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
application:name: SpringSecurity-JWT
# 热部署devtools:restart:enabled:trueadditional-paths: src/main/java
# 服务端口server:port:8090# 测试时将token有效期为5分钟token:expire:300000# 用于生成JWT的盐值jwt:secret:1234567890
项目启动和关闭日志
项目启动和关闭时控制台打印相关提示信息
/**
* @ApiNote: 项目启动和关闭时的日志打印
* @Author: 陌路
* @Date: 2023/2/18 9:46
* @Tool: Created by IntelliJ IDEA
*/@Slf4j@ComponentpublicclassAppStartAndStopimplementsApplicationRunner,DisposableBean{@Value("${server.port}")privateString port;/**
* @apiNote: 项目启动时运行此方法
*/@Overridepublicvoidrun(ApplicationArguments args){
log.info("==============项目启动成功!==============");
log.info("请访问地址:http://{}:{}",ApiUtils.getHostIp(), port);
log.info("=======================================");}/**
* @apiNote: 项目关闭时执行
* @return: void
*/@Overridepublicvoiddestroy(){
log.info("=======================================");
log.info("============程序已停止运行!============");
log.info("=======================================");}}
封装统一响应实体类
统一返回给前台的数据实体
packagecn.molu.security.jwt.vo;importcn.molu.security.jwt.utils.ApiUtils;importcom.fasterxml.jackson.annotation.JsonInclude;importlombok.NoArgsConstructor;importlombok.ToString;importjava.io.Serializable;importjava.util.HashMap;/**
* @ApiNote: 封装响应实体对象
* @Author: 陌路
* @Date: 2023/2/10 9:42
* @Tool: Created by IntelliJ IDEA.
*/@NoArgsConstructor// 生成无参构造方法@ToString(callSuper =true)// 重写toString方法@JsonInclude(JsonInclude.Include.NON_NULL)publicclassResult<T>extendsHashMap<String,Object>implementsSerializable{privatestaticfinallong serialVersionUID =2637614641937282252L;// 返回结果数据publicT result;// 返回成功失败标记publicstaticBoolean flag;// 返回成功状态码publicstaticfinalIntegerSUCCESS=200;// 返回失败状态码publicstaticfinalIntegerFIELD=500;/**
* @apiNote: 返回数据
* @param: code 状态码 [返回给前台的状态码]
* @param: msg 提示消息 [返回给前台得消息]
* @param: result 响应数据结果[返回给前台得结果]
* @param: flag 响应标志[true:成功,false:失败]
* @return: Result
*/publicstaticResultresult(Integer code,String msg,Object result,Boolean flag){Result r =newResult();
r.put("code", code);
r.put("msg", msg);
r.put("result", result);
r.put("flag", flag);
r.result = result;Result.flag = flag;return r;}/**
* @apiNote: 返回成功数据
* @param: msg 提示消息
* @param: result 响应数据结果
* @return: Result
*/publicstaticResultok(Integer code,String msg,Object result){returnresult(code, msg, result,true);}/**
* @apiNote: 返回成功数据
* @param: msg 提示消息
* @param: result 响应数据结果
* @return: Result
*/publicstaticResultok(String msg,Object result){returnresult(SUCCESS, msg, result,true);}/**
* @apiNote: 返回成功数据
* @param: result 响应数据结果
* @return: Result
*/publicstaticResultok(Object result){returnresult(SUCCESS,null, result,true);}/**
* @apiNote: 返回成功数据
* @param: msg 提示消息
* @return: Result
*/publicstaticResultok(String msg){returnresult(SUCCESS, msg,null,true);}/**
* @apiNote: 返回成功数据
* @return: Result
*/publicstaticResultok(){returnresult(SUCCESS,null,null,true);}/**
* @apiNote: 返回失败数据
* @param: msg 错误消息
* @param: result 响应数据结果
* @return: Result
*/publicstaticResulterr(Integer code,String msg,Object result){returnresult(code, msg, result,false);}/**
* @apiNote: 返回失败数据
* @param: code 响应状态码
* @param: msg 错误消息
* @return: Result
*/publicstaticResulterr(Integer code,String msg){returnresult(code, msg,null,false);}/**
* @apiNote: 返回失败数据
* @param: msg 提示消息
* @param: result 响应数据结果
* @return: Result
*/publicstaticResulterr(String msg,Object result){returnresult(FIELD, msg, result,false);}/**
* @apiNote: 返回失败数据
* @param: result 响应数据结果
* @return: Result
*/publicstaticResulterr(Object result){returnresult(FIELD,null, result,false);}/**
* @apiNote: 返回失败数据
* @param: msg 错误消息
* @return: Result
*/publicstaticResulterr(String msg){returnresult(FIELD, msg,null,false);}/**
* @apiNote: 返回失败数据
* @return: Result
*/publicstaticResulterr(){returnresult(FIELD,null,null,false);}/**
* @apiNote: 返回数据
* @param: [code, result, msg, flag]
* @return: cn.molu.api.vo.Result
*/publicstaticResultres(Integer code,Object result,String msg,boolean flag){returnresult(code, msg, result, flag);}/**
* @apiNote: 返回数据
* @param: [flag, result]
* @return: cn.molu.api.vo.Result
*/publicstaticResultres(boolean flag,Object result){returnresult(flag ?SUCCESS:FIELD,null, result, flag);}/**
* @apiNote: 返回数据
* @param: [flag, result]
* @return: cn.molu.api.vo.Result
*/publicstaticResultres(boolean flag,String msg,Object result){returnresult(flag ?SUCCESS:FIELD, msg, result, flag);}/**
* @apiNote: 返回数据
* @param: [flag, msg]
* @return: cn.molu.api.vo.Result
*/publicstaticResultres(boolean flag,String msg){returnresult(flag ?SUCCESS:FIELD, msg,null, flag);}/**
* @apiNote: 返回数据
* @param: [flag, msg]
* @return: cn.molu.api.vo.Result
*/publicstaticResultres(boolean flag){returnresult(flag ?SUCCESS:FIELD,null,null, flag);}/**
* @apiNote: 重写HashMap的put方法
* @param: [key, value]
* @return: Result
*/@OverridepublicResultput(String key,Object value){super.put(key, value);returnthis;}public<T>TgetResult(){returnApiUtils.getObj(this.result,null);}publicvoidsetRes(boolean flag,T result){this.flag = flag;this.result = result;put("flag", flag);put("result", result);}}
封装对象工具类
封装静态方法工具类,便于在项目中使用
/**
* @ApiNote: api通用工具类
* @Author: 陌路
* @Date: 2023/2/10 9:26
* @Tool: Created by IntelliJ IDEA.
*/publicclassApiUtils{/**
* @apiNote: 获取设备ip
* @return: String
*/publicstaticStringgetHostIp(){try{returnInetAddress.getLocalHost().getHostAddress();}catch(UnknownHostException e){return"127.0.0.1";}}/**
* @apiNote: 将对象转为字符串数据
* @param: [obj:带转换对象]
* @return: java.lang.String
*/publicstaticStringgetStr(Object obj){String str =Objects.nonNull(obj)?String.valueOf(obj).trim().replaceAll("\\s*|\r|\n|\t",""):"";return"null".equalsIgnoreCase(str)?"": str;}/**
* @apiNote: 将对象转为字符串数据, obj为空时返回defaultVal值
* @param: [obj, defaultVal]
* @return: java.lang.String
*/publicstaticStringgetStr(Object obj,String defaultVal){finalString str =getStr(obj);returnStringUtils.isBlank(str)? defaultVal : str;}/**
* @apiNote: 当对象obj为空时返回defaultVal值
* @param: [obj, defaultVal]
* @return: java.lang.Object
*/publicstatic<T>TgetObj(Object obj,Object defaultVal){finalString str =getStr(obj);if(StringUtils.isBlank(str)&&ObjUtil.isNull(defaultVal)){returnnull;}return(T)(StringUtils.isBlank(str)? defaultVal : obj);}/**
* @apiNote: 校验数据是否为空
* @param: [msg, val]
* @return: void
*/publicstaticvoidhasText(String msg,Object... val){if(ObjUtil.hasNull(val)||!ObjUtil.isAllNotEmpty(val)|| val.length ==0||StringUtils.isBlank(getStr(val))){Assert.hasText(null, msg);}}/**
* @apiNote: 向前台输出数据
* @param: [obj, response]
* @return: void
*/publicstaticvoidprintJsonMsg(Object obj,HttpServletResponse response){if(ObjUtil.isAllNotEmpty(obj, response)){
response.reset();
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");try(finalPrintWriter writer = response.getWriter()){
writer.print(obj);
writer.flush();}catch(IOException ignored){}}}/**
* @apiNote: 校验数据是否未空,为空则抛出异常
* @param: tipMsg:异常提示信息
* @param: params:需要校验的参数值
*/publicstaticvoidcheckParamsIsEmpty(String tipMsg,Object... params){if(ObjUtil.isNull(params)||!ObjUtil.isAllNotEmpty(params)){thrownewRuntimeException(getStr(tipMsg,"校验失败:参数值为空!"));}}}
Token工具类
封装
token
工具类,用于
生成token
和
解析token
数据
/**
* @ApiNote: token工具类
* @Author: 陌路
* @Date: 2023/02/10 16:00
* @Tool: Created by IntelliJ IDEA
*/@ComponentpublicclassTokenUtils{@ResourceprivateContextLoader contextLoader;@Value("${jwt.secret}")privateString secret;/**
* @apiNote: 生成token
* @param: userId 用户id
* @param: timeMillis 时间戳,每次生成的Token都不一样
* @return: token
*/publicStringcreateToken(Long userId,Long timeMillis){ApiUtils.checkParamsIsEmpty("生成Token失败,userId不能为空!", userId);
timeMillis = timeMillis ==null?System.currentTimeMillis(): timeMillis;String token =Jwts.builder().claim("userId", userId).claim("timeMillis", timeMillis).signWith(SignatureAlgorithm.HS256, secret).compact();
contextLoader.setCache(userId +"_KEY", token);return token;}/**
* @apiNote: 解析token数据
* @param: token
* @return: map
*/publicMap<String,Object>verifyToken(String token){returnStringUtils.isEmpty(token)?newHashMap<>():ApiUtils.getObj(Jwts.parser().setSigningKey(secret).parse(token).getBody(),newHashMap<>());}/**
* @apiNote: 根据token获取userId
* @param: token
* @return: userId
*/publicStringgetUserId(String token){returnApiUtils.getStr(verifyToken(token).get("userId"));}}
通过MyBatis-Plus操作数据库
/**
* @ApiNote: userMapper$
* @Author: 陌路
* @Date: 2023/2/18 11:13
* @Tool: Created by IntelliJ IDEA
*/@MapperpublicinterfaceUserMapperextendsBaseMapper<User>{}
封装缓存工具类
封装数据缓存类,用于缓存数据(
项目中使用redis做数据缓存
)
一般数据缓存是用redis
来做的,为了简便我这里就用了
Map
/**
* @ApiNote: 初始化缓存加载类
* @Author: 陌路
* @Date: 2023/2/10 9:29
* @Tool: Created by IntelliJ IDEA.
* @Desc: 正式开发中缓存数据应该放到redis中
*/@ComponentpublicclassContextLoader{// 缓存用户数据publicstaticfinalMap<String,LoginUser>CACHE_USER=newHashMap<>(2);// 缓存参数数据publicstaticfinalMap<String,Object>CACHE_PARAM=newHashMap<>(4);// 数据有效时长@Value("${token.expire}")privatelong expire;/**
* @apiNote: 根据token获取用户数据
* @param: [token]
* @return: cn.molu.api.pojo.User
*/publicLoginUsergetCacheUser(String token){if(StringUtils.isNotEmpty(token)&&CACHE_USER.containsKey(token)){finalLoginUser loginUser =ApiUtils.getObj(CACHE_USER.get(token),newLoginUser());Long expire =ApiUtils.getObj(loginUser.getExpire(),0);long currentTimeMillis =System.currentTimeMillis();if((expire > currentTimeMillis)){if(expire - currentTimeMillis <=this.expire){setCacheUser(token, loginUser);}return loginUser;}CACHE_USER.remove(token);}returnnewLoginUser();}/**
* @apiNote: 添加缓存数据到CACHE_USER中
* @param: [token, user]
* @return: cn.molu.api.pojo.User
*/publicvoidsetCacheUser(String token,LoginUser loginUser){if(StringUtils.isNotEmpty(token)){
loginUser.setExpire(System.currentTimeMillis()+ expire);CACHE_USER.put(token, loginUser);}}/**
* @apiNote: 向CACHE_PARAM中添加缓存数据
* @param: [key, val]
* @return: void
*/publicvoidsetCache(String key,Object val){if(StringUtils.isNotEmpty(key)){CACHE_PARAM.put(key, val);}}/**
* @apiNote: 删除CACHE_USER中的用户数据
* @param: key
* @return: void
*/publicvoiddeleteUser(String key){if(StringUtils.isNotBlank(key)&&this.CACHE_USER.containsKey(key)){this.CACHE_USER.remove(key);}}/**
* @apiNote: 删除CACHE_PARAM中的数据
* @param: key
* @return: void
*/publicvoiddeleteParam(String key){if(StringUtils.isNotEmpty(key)&&this.CACHE_PARAM.containsKey(key)){this.CACHE_PARAM.remove(key);}}}
用户对象实体类
用户对象,对应数据库中的
sys_user
表
/**
*@ApiNote: 用户对象实体类,对应数表sys_user
*@Author: 陌路
*@Date: 2023/2/18 20:46
*@Tool: Created by IntelliJ IDEA
*/@Data@NoArgsConstructor@TableName("sys_user")@ToString(callSuper =true)@JsonInclude(JsonInclude.Include.NON_EMPTY)publicclassUserimplementsSerializable{privatestaticfinallong serialVersionUID =-40356785423868312L;@TableIdprivateLong id;//主键privateString userName;//用户名privateString nickName;//昵称privateString password;//密码privateString status;//账号状态(0正常 1停用)privateString email;// 邮箱privateString phone;//手机号privateString sex;//用户性别(0男,1女,2未知)privateString avatar;//头像privateString userType;//用户类型(0管理员,1普通用户)privateLong createBy;//创建人的用户idprivateDate createTime;//创建时间privateLong updateBy;//更新人privateDate updateTime;//更新时间privateInteger delFlag;//删除标志(0代表未删除,1代表已删除)}
==SpringSecurity核心内容==
核心:用户认证(登录)
SpringSecurity
:登录业务需要实现
SpringSecurity
接口(
UserDetailsService
)中提供的方法(
loadUserByUsername
)并返回
SpringSecurity
提供的
UserDetails
接口对象
/**
* @ApiNote: 用户数据认证
* @Author: 陌路
* @Date: 2023/2/18 11:34
* @Tool: Created by IntelliJ IDEA
*/@Service("userDetailsImpl")publicclassUserDetailsImplimplementsUserDetailsService{@ResourceprivateUserMapper userMapper;/**
* @apiNote: 根据用户名获取用户数据
* @param: username 用户名
* @return: UserDetails
*/@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{// 根据用户名查询用户数据User user = userMapper.selectOne(newLambdaQueryWrapper<User>().eq(User::getUserName, username));ApiUtils.checkParamsIsEmpty("未获取到用户数据,请检查用户名和密码是否正确!", user);// 根据用户信息查询相关权限// TODO: 权限相关配置后面实现,目前先做认证 // 将用户数据封装到LoginUser中并返回returnnewLoginUser(user);}}
核心:实现接口封装用户数据
SpringSecurity
:存储当前登录用户数据,需要实现
SpringSecurity
提供的接口对象(
UserDetails
),通过
LoginUser
对象来接收
loadUserByUsername
返回的用户登录数据
/**
* @ApiNote: 封装登录用户数据
* @Author: 陌路
* @Date: 2023/2/18 11:55
* @Tool: Created by IntelliJ IDEA
*/@Data@NoArgsConstructor@AllArgsConstructor@ToString(callSuper =true)@JsonInclude(JsonInclude.Include.NON_NULL)publicclassLoginUserimplementsUserDetails{// 实现SpringSecurity提供的UserDetails接口来管理用户数据privateUser user;// 用户数据对象privateLong expire;// 过期时间privateString token;// token// 构造方法publicLoginUser(User user){this.user = user;}/**
* @apiNote: 获取当前登录用户信息
*/publicstaticLoginUsergetLoginUser(){LoginUser loginUser =(LoginUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();returnApiUtils.getObj(loginUser,newLoginUser());}/**
* @apiNote: 用户权限信息
*/@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){returnnull;}/**
* @apiNote: 获取用户密码
*/@OverridepublicStringgetPassword(){return user.getPassword();}/**
* @apiNote: 获取用户名
*/@OverridepublicStringgetUsername(){return user.getUserName();}/**
* @apiNote: 是否未过期(true:未过期,false:已过期)
*/@OverridepublicbooleanisAccountNonExpired(){returntrue;}/**
* @apiNote: 是否锁定
*/@OverridepublicbooleanisAccountNonLocked(){returntrue;}/**
* @apiNote: 是否超时(true:未超时,false:已超时)
*/@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}/**
* @apiNote: 当前用户是否可用(true:可用,false:不可用)
*/@OverridepublicbooleanisEnabled(){returntrue;}}
核心:SpringSecurity配置类
SpringSecurity
:核心配置类,用于配置自定义过滤器、拦截和放行用户请求
WebSecurityConfigurerAdapter
:此方法已过时,可使用
SecurityFilterChain
来配置,以下有说明
/**
* @ApiNote: SpringSecurity配置信息
* @Author: 陌路
* @Date: 2023/2/18 12:14
* @Tool: Created by IntelliJ IDEA
*///@ConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{// 注入自定义的过滤器,在用户名和密码认证之前执行(UsernamePasswordAuthenticationFilter之前)@ResourceprivateTokenAuthorityFilter tokenAuthorityFilter;/**
* @apiNote: 注入密码加密工具
*/@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}/**
* @apiNote: 注入AuthenticationManager对象来实现登录逻辑管理
*/@Bean@OverrideprotectedAuthenticationManagerauthenticationManager()throwsException{returnsuper.authenticationManager();}/**
* @apiNote: 配置请求认证和拦截
*/@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{// 关闭Security的CSRF功能防御
http.csrf().disable()// 不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 允许所有用户访问登录路径.antMatchers("/user/login").anonymous()//匿名访问(未登录未认证的)// 除以上请求路径外,其他所有请求都必须经过认证才能访问成功.anyRequest().authenticated();// 添加自定义的请求过滤器(tokenAuthorityFilter)并定义在指定哪个过滤器(UsernamePasswordAuthenticationFilter)执行前执行
http.addFilterBefore(tokenAuthorityFilter,UsernamePasswordAuthenticationFilter.class);}// 测试密码的加密和密码的验证publicstaticvoidmain(String[] args){BCryptPasswordEncoder passwordEncoder =newBCryptPasswordEncoder();// 加密后的密文,每次加密结果都不一样,因为加密时会生成随机盐值String encode = passwordEncoder.encode("123456");// 校验用户输入的密码和加密后的密码是否一样,一样返回true,否则返回falseboolean matches = passwordEncoder.matches("123456", encode);System.out.println("encode = "+ encode);System.out.println("matches = "+ matches);}}
以上对SpringSecurity配置的方法已过时
可以使用以下方法对SpringSecurity进行配置
/**
* @ApiNote: SpringSecurity配置信息
* @Author: 陌路
* @Date: 2023/2/18 12:14
* @Tool: Created by IntelliJ IDEA
*/@ConfigurationpublicclassSecurityConfiguration{@ResourceprivateTokenAuthorityFilter tokenAuthorityFilter;@ResourceprivateAuthenticationConfiguration authenticationConfiguration;@BeanpublicAuthenticationManagerauthenticationManager()throwsException{AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();return authenticationManager;}/**
* @apiNote: 注入密码加密工具
*/@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}@BeanpublicSecurityFilterChainfilterChain(HttpSecurity http)throwsException{
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 允许所有用户访问登录路径:anonymous(匿名访问,即允许未登录时访问,登录时则不允许访问).antMatchers("/user/login").anonymous()// 除以上请求路径外,其他所有请求都必须经过认证才能访问成功.anyRequest().authenticated().and()// 添加自定义的请求过滤器(tokenAuthorityFilter)并定义在指定哪个过滤器(UsernamePasswordAuthenticationFilter)执行前执行.addFilterBefore(tokenAuthorityFilter,UsernamePasswordAuthenticationFilter.class);// 添加异常处理器
http.exceptionHandling()// 认证异常处理器.authenticationEntryPoint(authenticationEntryPoint);// 运行跨域配置//http.cors();return http.build();}}
核心:自定义请求过滤器
SpringSecurity
:自定义请求过滤器需要继承
OncePerRequestFilter
类,并重写里面的
doFilterInternal
方法来实现具体的业务逻辑
/**
* @ApiNote: 请求过滤器:是否认证是否有权访问
* @Author: 陌路
* @Date: 2023/2/18 13:04
* @Tool: Created by IntelliJ IDEA
*/@ComponentpublicclassTokenAuthorityFilterextendsOncePerRequestFilter{@ResourceprivateTokenUtils tokenUtils;@ResourceprivateContextLoader contextLoader;/**
* @apiNote: 请求过滤器
*/@OverrideprotectedvoiddoFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throwsServletException,IOException{// 获取token数据String authorityToken =ApiUtils.getStr(request.getHeader("Authorization"));// token为空直接放行if(StringUtils.isBlank(authorityToken)){
filterChain.doFilter(request, response);return;}// 解析token数据得到userIdString userId = tokenUtils.getUserId(authorityToken);// 从缓存中获取用户信息LoginUser loginUser = contextLoader.getCacheUser(userId +"_TOKEN_"+ authorityToken);ApiUtils.checkParamsIsEmpty("请求失败,认证已过期!", loginUser, loginUser.getUser());// 将用户信息封装到SecurityContextHolder中//principal:用户数据,credentials:,authenticated:权限信息UsernamePasswordAuthenticationToken authenticationToken =newUsernamePasswordAuthenticationToken(loginUser,null,null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);}}
核心:SpringSecurity异常处理
认证失败:
- 实现
SpringSecurity
提供的AuthenticationEntryPoint
接口中的commence
方法来处理认证失败后的业务- 统一处理:统一返回JSON异常提示信息
- 在
SpringSecurity
配置类(SecurityConfiguration
)中添加http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
即可
/**
* @ApiNote: 认证失败处理类
* @Author: 陌路
* @Date: 2023/2/19 12:25
* @Tool: Created by IntelliJ IDEA
*/@ComponentpublicclassAuthenticationEntryPointImplimplementsAuthenticationEntryPoint{/**
* @apiNote: 认证失败处理
* @return: JSON(认证失败,请重新登录)
*/@Overridepublicvoidcommence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException)throwsIOException,ServletException{String authExceptionMessage = authException.getMessage();
authExceptionMessage =StringUtils.isBlank(ApiUtils.getStr(authExceptionMessage))?"认证失败,请重新登录!": authExceptionMessage;String jsonStr =JSONUtil.toJsonStr(Result.err(HttpStatus.UNAUTHORIZED.value(), authExceptionMessage));ApiUtils.printJsonMsg(jsonStr, response);}}
后台请求接口
用户请求后台接口:登录接口、查询用户信息接口、注销登录接口
/**
* @ApiNote: 请求接口控制器
* @Author: 陌路
* @Date: 2023/2/18 9:53
* @Tool: Created by IntelliJ IDEA
*/@RestController@RequestMapping("/user/*")publicclassIndexController{@ResourceprivateUserService userService;/**
* @apiNote: 获取用户列表
* @return: Result
*/@GetMapping("getUserList")publicResultgetUserList(){returnResult.ok(userService.queryList());}/**
* @apiNote: 用户登录接口
* @param: User对象实体
* @return: Result
*/@PostMapping("login")publicResultlogin(@RequestBodyUser user){return userService.login(user);}/**
* @apiNote: 用户退出登录
* @return: Result
*/@GetMapping("logout")publicResultlogout(){returnResult.res(userService.logout());}}
请求接口实现类
用户请求接口实现类型:登录、获取用户数据、注销登录
/**
* @ApiNote: userService$
* @Author: 陌路
* @Date: 2023/2/18 11:28
* @Tool: Created by IntelliJ IDEA
*/@Service("userService")publicclassUserServiceImplimplementsUserService{@Value("${token.expire}")privatelong expire;@ResourceprivateUserMapper userMapper;@ResourceprivateTokenUtils tokenUtils;@ResourceprivateContextLoader contextLoader;@ResourceprivateAuthenticationManager authenticationManager;/**
* @apiNote: 查询所有用户数据
*/publicList<User>queryList(){return userMapper.selectList(newLambdaQueryWrapper<User>().eq(User::getDelFlag,0));}/**
* @apiNote: 用户登录:缓存用户数据
* @param: User
* @return: Result
*/publicResultlogin(User user){UsernamePasswordAuthenticationToken authenticationToken =newUsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);ApiUtils.checkParamsIsEmpty("登录失败!", authenticate);LoginUser loginUser =ApiUtils.getObj(authenticate.getPrincipal(),newLoginUser());long currentTimeMillis =System.currentTimeMillis();String token = tokenUtils.createToken(loginUser.getUser().getId(), currentTimeMillis);
loginUser.setToken(token);
loginUser.setExpire(currentTimeMillis + expire);
contextLoader.setCacheUser(loginUser.getUser().getId()+"_TOKEN_"+ token, loginUser);returnResult.ok("登录成功!", token);}/**
* @apiNote: 用户退出登录,删除用户缓存数据
*/publicbooleanlogout(){LoginUser loginUser =LoginUser.getLoginUser();Long id = loginUser.getUser().getId();String token = loginUser.getToken();
contextLoader.deleteUser(id +"_TOKEN_"+ token);
contextLoader.deleteParam(id +"_KEY");returntrue;}}
项目接口调用实例
在请求体中输入用户名和密码进行登录(登录时请求头不需要携带token)请求
/user/login
接口,登录成功!
请求头中携带
token
,请求
/user/getUserList
接口,获取用户列表数据,请求成功!
请求头中携带
token
请求
/user/logout
接口退出登录,请求成功!
退出登录后,携带
toekn
再次访问
/user/getUserList
获取用户列表接口,可以看到请求被拒绝访问,后台校验失败,提示
请求失败,认证已过期!
**到此
SpringSecurity
登录认证部分已结束,希望这篇文章对您有所帮助**
**下一篇
SpringSecurity
的权限校验**
SpringSecurity的权限校验详解说明(附完整代码)
https://blog.csdn.net/qq_51076413/article/details/129106824
版权归原作者 .陌路 所有, 如有侵权,请联系我们删除。