0


SpringSecurity的安全认证的详解说明(附完整代码)

**

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

标签: 安全 mybatis java

本文转载自: https://blog.csdn.net/qq_51076413/article/details/129102660
版权归原作者 .陌路 所有, 如有侵权,请联系我们删除。

“SpringSecurity的安全认证的详解说明(附完整代码)”的评论:

还没有评论