0


SpringBoot3整合SpringSecurity,实现自定义接口权限过滤

接口权限过滤是指对于某些接口或功能,系统通过设定一定的权限规则,只允许经过身份认证且拥有相应权限的用户或应用程序进行访问和操作。这种技术可以有效地保护系统资源和数据安全,防止未授权的用户或程序进行恶意操作或非法访问。通常情况下,接口权限过滤需要配合其他安全措施一起使用,例如加密、身份认证、审计等,以达到综合保护的效果。

本文将使用 SpringBoot3 整合 SpringSecurity,实现自定义接口权限过滤,源码在 项目仓库 中,需要者可自助参考。

一、导入依赖

在实现自定义接口权限过滤之前,首先要导入依赖,首先是 SpringBoot 父依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.0</version></parent>

接下来是 Spring Boot Web 依赖,用于提供最基本的接口支持。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

在实现登录时需要设计 Token,整合 Redis,所以需要加上以下依赖。

<dependency><groupId>org.springframework.integration</groupId><artifactId>spring-integration-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.21.3</version></dependency>

最后就是 Spring Security 依赖,用于实现权限控制。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

Spring Boot 3.1 版本对应的 Spring Security 依赖为 6.1.0 版本,废弃了 WebSecurityConfigurerAdapter 类,配置内容和 Spring Security 5 有着明显不同,版本依赖如下图所示。

在这里插入图片描述


二、编写登录提示接口

要实现自定义接口权限过滤,首先要定义一个登录提示接口,用于被拦截时返回用户的数据,如下图所示。

在这里插入图片描述

请同学们新建一个控制器

SecurityController

,定义这个

/zwz/common/needLogin

接口,代码如下。

@RestController@RequestMapping("/zwz/common")@Api(tags ="公共接口")@TransactionalpublicclassSecurityController{@RequestMapping(value ="/needLogin", method =RequestMethod.GET)@ApiOperation(value ="未登录返回的数据")publicResult<Object>needLogin(){returnResultUtil.error(401,"登录失效");}}

三、编写登录成功处理函数

很多同学会问,Spring Security项目中登录接口在哪里,如何实现登录功能,此时…

在 Spring Security 中,我们只需要对登录接口、登录成功/失败回调、过滤器等内容进行配置即可,开发者无需关注登录的具体实现。

请同学们新建

AuthenticationSuccessHandler

类,继承于 Spring Security 的

SavedRequestAwareAuthenticationSuccessHandler

类 ,用于回调用户登录成功的方法,。

@ApiOperation(value ="登录成功回调")@Slf4j@ComponentpublicclassAuthenticationSuccessHandlerextendsSavedRequestAwareAuthenticationSuccessHandler{}

3.1 判断是否保存登录

请同学们首先重写

AuthenticationSuccessHandler

类中的

onAuthenticationSuccess

方法,这个方法用于实现登录成功回调

@Override@ApiOperation(value ="登录成功回调")@SystemLog(about ="登录系统", type =LogType.LOGIN)publicvoidonAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication ac)throwsIOException,ServletException{}

在实现登录成功回调的开始,首先要判断用户是否勾选了自动登录,获取是否登录的代码如下。

String saveLogin = request.getParameter(ZwzLoginProperties.SAVE_LOGIN_PRE);Boolean saveLoginFlag =false;if(!ZwzNullUtils.isNull(saveLogin)&&Objects.equals(saveLogin,"true")){
    saveLoginFlag =true;}

这样就可以把用户是否保存登录的标识保存在

saveLoginFlag

变量中,以备后续保存 Token 使用。

3.2 保存用户的信息和菜单权限

用户登录成功后,需要加载用户的菜单,此时需要把用户的账号和菜单保存到缓存中

用户第二次免登进入系统,即可快速从缓存中获取菜单数据,加快用户的菜单加载速度,降低数据库的读取压力

首先定义一个

TokenUser

类,用于存储临时用户信息(账号、菜单权限),代码如下。

@ApiOperation(value ="临时用户类")@Data@AllArgsConstructor@NoArgsConstructorpublicclassTokenUserimplementsSerializable{privatestaticfinallong serialVersionUID =1L;@ApiModelProperty(value ="用户名")privateString username;@ApiModelProperty(value ="拥有的菜单权限")privateList<String> permissions;@ApiModelProperty(value ="是否自动登录")privateBoolean saveLogin;}

接着继续在

onAuthenticationSuccess

方法中实现菜单拉取。

List<String> permissionsList =newArrayList<>();List<GrantedAuthority> authorities =(List<GrantedAuthority>)((UserDetails)ac.getPrincipal()).getAuthorities();for(GrantedAuthority g : authorities){
    permissionsList.add(g.getAuthority());}String username =((UserDetails)ac.getPrincipal()).getUsername();TokenUser user =newTokenUser(username, permissionsList, saveLoginFlag);

上述代码将指定用户的菜单权限拉取出来,保存到

TokenUser

临时用户类中,以备后续调用。

3.3 单点登录处理

单点登录(SSO)是一种身份认证的技术或协议,允许用户在多个应用系统中使用同一组凭据(例如用户名和密码),只需进行一次身份验证即可访问所有的应用系统,从而实现了不同应用系统之间的身份认证信息共享。这种技术可以提高用户的使用便利性,避免重复登录,减少密码管理负担,同时也能够增强系统的安全性,降低密码泄露和被攻击的风险。

对于一般的管理系统来说,都支持单点登录。

通俗点来说,单点登录就是单个账号只允许一个地点登录,电脑 A 登录时,如果登录到电脑 B,此时电脑 A 会被 “顶” 下线。

接着继续在

onAuthenticationSuccess

方法中实现单点登录。

publicstaticfinalString HTTP_TOKEN_PRE ="ZWZ_TOKEN_PRE:";publicstaticfinalString USER_TOKEN_PRE ="ZWZ_USER_TOKEN:";
String oldToken = redisTemplate.opsForValue().get(ZwzLoginProperties.USER_TOKEN_PRE + username);if(StrUtil.isNotBlank(oldToken)){
    redisTemplate.delete(ZwzLoginProperties.HTTP_TOKEN_PRE + oldToken);}

如果老的 Token 还存在,就把老 Token 删除,即可实现单点登录功能。

3.4 持久化登录信息

最后,将用户的数据持久化到 Redis 中,将 Token 返回给前端,存储到 Cookie 中,前端就可以使用 Token 免登进入、访问系统。

首先是 Token 的生成,可以采用 UUID 类辅助生成,代码如下。

String token = UUID.randomUUID().toString();

注入 Redis 工具类,代码如下。

importorg.springframework.data.redis.core.StringRedisTemplate;@AutowiredprivateStringRedisTemplate redisTemplate;

接着根据 3.1 步中的

是否保存

结果,进行持久化的处理,同学们可以自定义 Token 的前缀

USER_TOKEN_PRE 

publicstaticfinalString HTTP_TOKEN_PRE ="ZWZ_TOKEN_PRE:";publicstaticfinalString USER_TOKEN_PRE ="ZWZ_USER_TOKEN:";
if(saveLoginFlag){
    redisTemplate.opsForValue().set(ZwzLoginProperties.USER_TOKEN_PRE + username, token,30,TimeUnit.DAYS);
    redisTemplate.opsForValue().set(ZwzLoginProperties.HTTP_TOKEN_PRE + token, JSON.toJSONString(user),30,TimeUnit.DAYS);}else{
    redisTemplate.opsForValue().set(ZwzLoginProperties.USER_TOKEN_PRE + username, token,60,TimeUnit.MINUTES);
    redisTemplate.opsForValue().set(ZwzLoginProperties.HTTP_TOKEN_PRE + token, JSON.toJSONString(user),60,TimeUnit.MINUTES);}ResponseUtil.out(response,ResponseUtil.resultMap(true,200,"登录成功", token));

当用户勾选了保存登录,系统保存 Token 30天,即用户可以在未来 30 天内,免登进入系统。

如果用户没有勾选保存登录,系统保存 Token 60 分钟,即用户可以在未来 1 小时内,免登进入系统。

完整代码如下。

@ApiOperation(value ="登录成功回调")@Slf4j@ComponentpublicclassAuthenticationSuccessHandlerextendsSavedRequestAwareAuthenticationSuccessHandler{@AutowiredprivateZwzLoginProperties tokenProperties;@AutowiredprivateStringRedisTemplate redisTemplate;privatestaticfinalboolean RESPONSE_SUCCESS_FLAG =true;privatestaticfinalint RESPONSE_SUCCESS_CODE =200;privatestaticfinalString TOKEN_REPLACE_STR_FRONT ="-";privatestaticfinalString TOKEN_REPLACE_STR_BACK ="";@Override@ApiOperation(value ="登录成功回调")@SystemLog(about ="登录系统", type =LogType.LOGIN)publicvoidonAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication ac)throwsIOException,ServletException{String saveLogin = request.getParameter(ZwzLoginProperties.SAVE_LOGIN_PRE);Boolean saveLoginFlag =false;if(!ZwzNullUtils.isNull(saveLogin)&&Objects.equals(saveLogin,"true")){
            saveLoginFlag =true;}List<String> permissionsList =newArrayList<>();List<GrantedAuthority> authorities =(List<GrantedAuthority>)((UserDetails)ac.getPrincipal()).getAuthorities();for(GrantedAuthority g : authorities){
            permissionsList.add(g.getAuthority());}String token = UUID.randomUUID().toString().replace(TOKEN_REPLACE_STR_FRONT, TOKEN_REPLACE_STR_BACK);String username =((UserDetails)ac.getPrincipal()).getUsername();TokenUser user =newTokenUser(username, permissionsList, saveLoginFlag);// 判断是否存储菜单权限if(!tokenProperties.getSaveRoleFlag()){
            user.setPermissions(null);}// 单点登录判断if(tokenProperties.getSsoFlag()){String oldToken = redisTemplate.opsForValue().get(ZwzLoginProperties.USER_TOKEN_PRE + username);if(StrUtil.isNotBlank(oldToken)){
                redisTemplate.delete(ZwzLoginProperties.HTTP_TOKEN_PRE + oldToken);}}if(saveLoginFlag){
            redisTemplate.opsForValue().set(ZwzLoginProperties.USER_TOKEN_PRE + username, token, tokenProperties.getUserSaveLoginTokenDays(),TimeUnit.DAYS);
            redisTemplate.opsForValue().set(ZwzLoginProperties.HTTP_TOKEN_PRE + token, JSON.toJSONString(user), tokenProperties.getUserSaveLoginTokenDays(),TimeUnit.DAYS);}else{
            redisTemplate.opsForValue().set(ZwzLoginProperties.USER_TOKEN_PRE + username, token, tokenProperties.getUserTokenInvalidDays(),TimeUnit.MINUTES);
            redisTemplate.opsForValue().set(ZwzLoginProperties.HTTP_TOKEN_PRE + token, JSON.toJSONString(user), tokenProperties.getUserTokenInvalidDays(),TimeUnit.MINUTES);}ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_SUCCESS_FLAG,RESPONSE_SUCCESS_CODE,"登录成功", token));}}

四、编写登录失败处理函数

请同学们新建

AuthenticationFailHandler

类,继承于 Spring Security 的

SimpleUrlAuthenticationFailureHandler

类 ,用于回调用户登录失败 的方法,。

@ApiOperation(value ="登录失败回调")@Slf4j@ComponentpublicclassAuthenticationFailHandlerextendsSimpleUrlAuthenticationFailureHandler{}

4.1 判断是否密码错误

请同学们首先重写

AuthenticationFailHandler

类中的

onAuthenticationFailure

方法,这个方法用于实现登录失败回调

@Override@ApiOperation(value ="登录失败回调")publicvoidonAuthenticationFailure(HttpServletRequest request,HttpServletResponse response,AuthenticationException exception){}

在实现登录失败回调的开始,首先要判断用户是否输入了错误的密码。

其中错误的密码包括:

  1. 用户密码输入错误。(UsernameNotFoundException 异常)
  2. 用户输入了正确的密码,但没有加密。(BadCredentialsException 异常)
if(exception instanceofBadCredentialsException|| exception instanceofUsernameNotFoundException){recordLoginTime(request.getParameter("username:"));String failTimesStr = stringRedisTemplate.opsForValue().get("LOGIN_FAIL_TIMES_PRE:"+ request.getParameter("username:"));// 已错误的次数int userFailTimes =0;if(!ZwzNullUtils.isNull(failTimesStr)){
        userFailTimes =Integer.parseInt(failTimesStr);}int restLoginTime =10- userFailTimes;if(restLoginTime <5&& restLoginTime >0){ResponseUtil.out(response,ResponseUtil.resultMap(false,500,"账号密码不正确,还能尝试登录"+ restLoginTime +"次"));}elseif(restLoginTime <1){ResponseUtil.out(response,ResponseUtil.resultMap(false,500,"重试超限,请您"+10+"分后再登录"));}else{ResponseUtil.out(response,ResponseUtil.resultMap(false,500,"账号密码不正确"));}}

默认设定 10 次登录试错次数,如果超限会被临时禁止登录

其中

recordLoginTime

方法用于查询登录失败的次数,代码如下。

@ApiOperation(value ="查询登录失败的次数")publicbooleanrecordLoginTime(String username){String loginFailTimeStr = stringRedisTemplate.opsForValue().get(LOGIN_FAIL_TIMES_PRE + username);int loginFailTime =0;// 已错误次数if(!ZwzNullUtils.isNull(loginFailTimeStr)){
        loginFailTime =Integer.parseInt(loginFailTimeStr)+1;}
    stringRedisTemplate.opsForValue().set("LOGIN_FAIL_TIMES_PRE:"+ username, loginFailTime +"",10,TimeUnit.MINUTES);if(loginFailTime >=10){
        stringRedisTemplate.opsForValue().set("userLoginDisableFlag:"+username,"fail",10,TimeUnit.MINUTES);returnfalse;}returntrue;}

4.2 判断是否系统鉴权失败

接着判断是否是自定义异常

ZwzAuthException

如果属于自定义异常,则抛出自定义异常的信息,代码如下。

if(exception instanceofZwzAuthException){ResponseUtil.out(response,ResponseUtil.resultMap(false,500,((ZwzAuthException) exception).getMsg()));}

其中自定义异常定义如下。

@ApiOperation(value ="自定义异常")publicclassZwzAuthExceptionextendsInternalAuthenticationServiceException{privatestaticfinallong serialVersionUID =1L;privatestaticfinalString DEFAULT_MSG ="系统鉴权失败";@ApiModelProperty(value ="异常消息内容")privateString msg;publicZwzAuthException(String msg){super(msg);this.msg = msg;}publicZwzAuthException(){super(DEFAULT_MSG);this.msg = DEFAULT_MSG;}publicZwzAuthException(String msg,Throwable t){super(msg, t);this.msg = msg;}publicStringgetMsg(){return msg;}publicvoidsetMsg(String msg){this.msg = msg;}}

4.3 判断账户是否被禁用

管理系统一般支持账户禁用功能,即把

status

值设定为某个状态,如

-1

接着判断是否是账户禁用异常

DisabledException

如果属于账户禁用,则给与提示,代码如下。

if(exception instanceofDisabledException){ResponseUtil.out(response,ResponseUtil.resultMap(false,500,"账户处于禁用状态,无法登录"));}

用户是否禁用通常是实体类的一个字段,如

status

4.4 其他登录失败处理

如果不属于上面的三种情况,则给与通用的报错提示,代码如下。

else{ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,"系统当前不能登录,请稍后再试"));}

完整代码如下。

@ApiOperation(value ="登录失败回调")@Slf4j@ComponentpublicclassAuthenticationFailHandlerextendsSimpleUrlAuthenticationFailureHandler{@AutowiredprivateZwzLoginProperties tokenProperties;@AutowiredprivateStringRedisTemplate stringRedisTemplate;privatestaticfinalString LOGIN_FAIL_TIMES_PRE ="LOGIN_FAIL_TIMES_PRE:";privatestaticfinalString REQUEST_PARAMETER_USERNAME ="username:";privatestaticfinalboolean RESPONSE_FAIL_FLAG =false;privatestaticfinalint RESPONSE_FAIL_CODE =500;@ApiOperation(value ="查询登录失败的次数")publicbooleanrecordLoginTime(String username){String loginFailTimeStr = stringRedisTemplate.opsForValue().get(LOGIN_FAIL_TIMES_PRE + username);int loginFailTime =0;// 已错误次数if(!ZwzNullUtils.isNull(loginFailTimeStr)){
            loginFailTime =Integer.parseInt(loginFailTimeStr)+1;}
        stringRedisTemplate.opsForValue().set(LOGIN_FAIL_TIMES_PRE + username, loginFailTime +"", tokenProperties.getLoginFailMaxThenLockTimes(),TimeUnit.MINUTES);if(loginFailTime >= tokenProperties.getMaxLoginFailTimes()){
            stringRedisTemplate.opsForValue().set("userLoginDisableFlag:"+username,"fail", tokenProperties.getLoginFailMaxThenLockTimes(),TimeUnit.MINUTES);returnfalse;}returntrue;}@Override@ApiOperation(value ="登录失败回调")publicvoidonAuthenticationFailure(HttpServletRequest request,HttpServletResponse response,AuthenticationException exception){if(exception instanceofBadCredentialsException|| exception instanceofUsernameNotFoundException){recordLoginTime(request.getParameter(REQUEST_PARAMETER_USERNAME));String failTimesStr = stringRedisTemplate.opsForValue().get(LOGIN_FAIL_TIMES_PRE + request.getParameter(REQUEST_PARAMETER_USERNAME));//已错误的次数int userFailTimes =0;if(!ZwzNullUtils.isNull(failTimesStr)){
                userFailTimes =Integer.parseInt(failTimesStr);}int restLoginTime = tokenProperties.getMaxLoginFailTimes()- userFailTimes;if(restLoginTime <5&& restLoginTime >0){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,"账号密码不正确,还能尝试登录"+ restLoginTime +"次"));}elseif(restLoginTime <1){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,"重试超限,请您"+ tokenProperties.getLoginFailMaxThenLockTimes()+"分后再登录"));}else{ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,"账号密码不正确"));}}elseif(exception instanceofZwzAuthException){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,((ZwzAuthException) exception).getMsg()));}elseif(exception instanceofDisabledException){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,"账户处于禁用状态,无法登录"));}else{ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,"系统当前不能登录,请稍后再试"));}}}

五、编写过滤器

5.1 基于 Token 的权限过滤

请同学们新建

JwtTokenOncePerRequestFilter

过滤器,继承于

OncePerRequestFilter

,重写

doFilterInternal

过滤方法,代码如下。

@ApiOperation(value ="自定义权限过滤")@Slf4jpublicclassJwtTokenOncePerRequestFilterextendsOncePerRequestFilter{privateSecurityUtil securityUtil;@AutowiredprivateRedisTemplateHelper redisTemplate;privateZwzLoginProperties zwzLoginProperties;privatestaticfinalboolean RESPONSE_FAIL_FLAG =false;privatestaticfinalint RESPONSE_NO_ROLE_CODE =401;@OverrideprotectedvoiddoFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throwsServletException,IOException{String tokenHeader = request.getHeader(ZwzLoginProperties.HTTP_HEADER);if(ZwzNullUtils.isNull(tokenHeader)){
            tokenHeader = request.getParameter(ZwzLoginProperties.HTTP_HEADER);}if(ZwzNullUtils.isNull(tokenHeader)){
            filterChain.doFilter(request, response);return;}try{UsernamePasswordAuthenticationToken token =getUsernamePasswordAuthenticationToken(tokenHeader, response);SecurityContextHolder.getContext().setAuthentication(token);}catch(Exception e){
            log.warn("自定义权限过滤失败"+ e);}
        filterChain.doFilter(request, response);}@ApiOperation(value ="判断登录是否失效")privateUsernamePasswordAuthenticationTokengetUsernamePasswordAuthenticationToken(String header,HttpServletResponse response){String userName =null;String tokenInRedis = redisTemplate.get(ZwzLoginProperties.HTTP_TOKEN_PRE + header);if(ZwzNullUtils.isNull(tokenInRedis)){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_NO_ROLE_CODE,"登录状态失效,需要重登!"));returnnull;}TokenUser tokenUser =JSONObject.parseObject(tokenInRedis,TokenUser.class);
        userName = tokenUser.getUsername();List<GrantedAuthority> permissionList =newArrayList<>();if(zwzLoginProperties.getSaveRoleFlag()){for(String permission : tokenUser.getPermissions()){
                permissionList.add(newSimpleGrantedAuthority(permission));}}else{
            permissionList = securityUtil.getCurrUserPerms(userName);}if(!tokenUser.getSaveLogin()){
            redisTemplate.set(ZwzLoginProperties.USER_TOKEN_PRE + userName, header, zwzLoginProperties.getUserTokenInvalidDays(),TimeUnit.MINUTES);
            redisTemplate.set(ZwzLoginProperties.HTTP_TOKEN_PRE + header, tokenInRedis, zwzLoginProperties.getUserTokenInvalidDays(),TimeUnit.MINUTES);}if(!ZwzNullUtils.isNull(userName)){User user =newUser(userName,"", permissionList);returnnewUsernamePasswordAuthenticationToken(user,null, permissionList);}returnnull;}publicJwtTokenOncePerRequestFilter(RedisTemplateHelper redis,SecurityUtil securityUtil,ZwzLoginProperties zwzLoginProperties){this.redisTemplate = redis;this.securityUtil = securityUtil;this.zwzLoginProperties = zwzLoginProperties;}}

以上代码用于判断用户的 Token 状态,依次判断用户登录是否还处于有效状态

5.2 图形验证码过滤

请同学们新建

ImageValidateFilter

过滤器,继承于

OncePerRequestFilter

,重写

doFilterInternal

过滤方法。

过滤器首先需要读取需要验证的接口,如果无需验证则放行,代码如下。

Boolean filterFlag =false;for(String requestURI : captchaProperties.getVerification()){if(pathMatcher.match(requestURI, request.getRequestURI())){
        filterFlag =true;break;}}if(!filterFlag){
    filterChain.doFilter(request, response);return;}

如果确定需要进行验证码过滤,则尝试读取验证码 ID

captchaId

和输入的验证码值

code

,在 Redis 进行取值判断。

过滤器需要判断验证码是否为空、是否过期、是否正确,最后给与处理,代码如下。

String verificationCodeId = request.getParameter("captchaId");String userInputCode = request.getParameter("code");if(ZwzNullUtils.isNull(userInputCode)||ZwzNullUtils.isNull(verificationCodeId)){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_CODE_FAIL_CODE,"验证码为空"));return;}String codeAnsInRedis = redisTemplate.opsForValue().get(verificationCodeId);if(ZwzNullUtils.isNull(codeAnsInRedis)){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_CODE_FAIL_CODE,"已过期的验证码,需要重新填写"));return;}if(!Objects.equals(codeAnsInRedis.toLowerCase(),userInputCode.toLowerCase())){ResponseUtil.out(response,ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_CODE_FAIL_CODE,"验证码不正确"));return;}
redisTemplate.delete(verificationCodeId);
filterChain.doFilter(request, response);

六、WebSecurityConfig 类整合

6.1 WebSecurityConfig 类配置

请同学们创建

WebSecurityConfig

类,增加

@Configuration

注解,定义为配置类,代码如下。

@ApiOperation(value ="SpringSecurity配置类")@Configuration@EnableMethodSecuritypublicclassWebSecurityConfig{}

接着创建

securityFilterChain

方法,配置 Spring Security。

首先是配置白名单,即配置不需要拦截的接口明细,代码如下。

.requestMatchers("/zwz/dictData/getByType/**","/zwz/file/view/**","/zwz/user/regist","/zwz/common/**","/*/*.js","/*/*.css","/*/*.png","/*/*.ico","/swagger-ui.html")

接着配置提示登录页面和登录接口,代码如下。

.formLogin().loginPage("/zwz/common/needLogin").loginProcessingUrl("/zwz/login").permitAll()

接着配置登录成功回调处理类,代码如下。

.successHandler(authenticationSuccessHandler)

接着配置登录失败回调处理类,代码如下。

.failureHandler(authenticationFailHandler)

最后配置过滤器,包括自定义权限过滤器和图形验证码过滤器,代码如下。

.addFilterBefore(authenticationJwtTokenFilter(),UsernamePasswordAuthenticationFilter.class).addFilterBefore(imageValidateFilter,UsernamePasswordAuthenticationFilter.class);

完整代码如下所示。

@ApiOperation(value ="SpringSecurity配置类")@Configuration@EnableMethodSecuritypublicclassWebSecurityConfig{@AutowiredprivateZwzLoginProperties zwzLoginProperties;@AutowiredprivateUserDetailsServiceImpl userDetailsService;@AutowiredprivateAuthenticationSuccessHandler authenticationSuccessHandler;@AutowiredprivateAuthenticationFailHandler authenticationFailHandler;@AutowiredprivateZwzAccessDeniedHandler zwzAccessDeniedHandler;@AutowiredprivateImageValidateFilter imageValidateFilter;@AutowiredprivateRedisTemplateHelper redisTemplate;@AutowiredprivateSecurityUtil securityUtil;@BeanpublicSecurityFilterChainsecurityFilterChain(HttpSecurity http)throwsException{

        http.authorizeHttpRequests().requestMatchers("/zwz/dictData/getByType/**","/zwz/file/view/**","/zwz/user/regist","/zwz/common/**","/*/*.js","/*/*.css","/*/*.png","/*/*.ico","/swagger-ui.html").permitAll().and().formLogin().loginPage("/zwz/common/needLogin").loginProcessingUrl("/zwz/login").permitAll().successHandler(authenticationSuccessHandler).failureHandler(authenticationFailHandler).and().headers().frameOptions().disable().and().logout().permitAll().and().authorizeHttpRequests().anyRequest().authenticated().and().cors().and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().exceptionHandling().accessDeniedHandler(zwzAccessDeniedHandler).and().authenticationProvider(authenticationProvider()).addFilterBefore(authenticationJwtTokenFilter(),UsernamePasswordAuthenticationFilter.class).addFilterBefore(imageValidateFilter,UsernamePasswordAuthenticationFilter.class);return http.build();}@BeanpublicUserDetailsServiceuserDetailsService(){return username -> userDetailsService.loadUserByUsername(username);}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}@BeanpublicAuthenticationProviderauthenticationProvider(){DaoAuthenticationProvider authProvider =newDaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());return authProvider;}@BeanpublicAuthenticationManagerauthenticationManager(AuthenticationConfiguration config)throwsException{return config.getAuthenticationManager();}@BeanpublicJwtTokenOncePerRequestFilterauthenticationJwtTokenFilter()throwsException{returnnewJwtTokenOncePerRequestFilter(redisTemplate, securityUtil, zwzLoginProperties);}}

6.2 测试

请同学们运行项目后端,首先使用浏览器访问

http://localhost:8081/

,系统自动重定向到登录提示页面,如下图所示。

在这里插入图片描述

接着访问免登接口

http://localhost:8081/zwz/dictData/getByType/sex

,发现可以正常读取数据。

在这里插入图片描述
最终,本文成功将 SpringBoot3 整合了 SpringSecurity,实现了自定义接口权限过滤。

在这里插入图片描述

标签: spring boot spring java

本文转载自: https://blog.csdn.net/qq_41464123/article/details/130966414
版权归原作者 Designer 小郑 所有, 如有侵权,请联系我们删除。

“SpringBoot3整合SpringSecurity,实现自定义接口权限过滤”的评论:

还没有评论