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

这篇具有很好参考价值的文章主要介绍了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 有着明显不同,版本依赖如下图所示。

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


二、编写登录提示接口

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

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

请同学们新建一个控制器 SecurityController,定义这个 /zwz/common/needLogin 接口,代码如下。

@RestController
@RequestMapping("/zwz/common")
@Api(tags = "公共接口")
@Transactional
public class SecurityController {

    @RequestMapping(value = "/needLogin", method = RequestMethod.GET)
    @ApiOperation(value = "未登录返回的数据")
    public Result<Object> needLogin(){
        return ResultUtil.error(401, "登录失效");
    }
}

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

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

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

请同学们新建 AuthenticationSuccessHandler 类,继承于 Spring Security 的 SavedRequestAwareAuthenticationSuccessHandler 类 ,用于回调用户登录成功的方法,。

@ApiOperation(value = "登录成功回调")
@Slf4j
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
}

3.1 判断是否保存登录

请同学们首先重写 AuthenticationSuccessHandler 类中的 onAuthenticationSuccess 方法,这个方法用于实现登录成功回调

@Override
@ApiOperation(value = "登录成功回调")
@SystemLog(about = "登录系统", type = LogType.LOGIN)
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication ac) throws IOException, 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
@NoArgsConstructor
public class TokenUser implements Serializable{

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "拥有的菜单权限")
    private List<String> permissions;

    @ApiModelProperty(value = "是否自动登录")
    private Boolean saveLogin;
}

接着继续在 onAuthenticationSuccess 方法中实现菜单拉取。

List<String> permissionsList = new ArrayList<>();
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 = new TokenUser(username, permissionsList, saveLoginFlag);

上述代码将指定用户的菜单权限拉取出来,保存到 TokenUser 临时用户类中,以备后续调用。

3.3 单点登录处理

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

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

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

接着继续在 onAuthenticationSuccess 方法中实现单点登录。

public static final String HTTP_TOKEN_PRE = "ZWZ_TOKEN_PRE:";

public static final String 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 工具类,代码如下。

import org.springframework.data.redis.core.StringRedisTemplate;

@Autowired
private StringRedisTemplate redisTemplate;

接着根据 3.1 步中的 是否保存 结果,进行持久化的处理,同学们可以自定义 Token 的前缀 USER_TOKEN_PRE

public static final String HTTP_TOKEN_PRE = "ZWZ_TOKEN_PRE:";

public static final String 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
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Autowired
    private ZwzLoginProperties tokenProperties;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final boolean RESPONSE_SUCCESS_FLAG = true;

    private static final int RESPONSE_SUCCESS_CODE = 200;

    private static final String TOKEN_REPLACE_STR_FRONT = "-";

    private static final String TOKEN_REPLACE_STR_BACK = "";

    @Override
    @ApiOperation(value = "登录成功回调")
    @SystemLog(about = "登录系统", type = LogType.LOGIN)
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication ac) throws IOException, 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 = new ArrayList<>();
        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 = new TokenUser(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
@Component
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
}

4.1 判断是否密码错误

请同学们首先重写 AuthenticationFailHandler 类中的 onAuthenticationFailure 方法,这个方法用于实现登录失败回调

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

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

其中错误的密码包括:

  1. 用户密码输入错误。(UsernameNotFoundException 异常)
  2. 用户输入了正确的密码,但没有加密。(BadCredentialsException 异常)
if (exception instanceof BadCredentialsException || exception instanceof UsernameNotFoundException) {
    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 + "次"));
    } else if(restLoginTime < 1) {
        ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"重试超限,请您" + 10 + "分后再登录"));
    } else {
        ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"账号密码不正确"));
    }
}

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

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

@ApiOperation(value = "查询登录失败的次数")
public boolean recordLoginTime(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);
        return false;
    }
    return true;
}

4.2 判断是否系统鉴权失败

接着判断是否是自定义异常 ZwzAuthException如果属于自定义异常,则抛出自定义异常的信息,代码如下。

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

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

@ApiOperation(value = "自定义异常")
public class ZwzAuthException extends InternalAuthenticationServiceException {

    private static final long serialVersionUID = 1L;

    private static final String DEFAULT_MSG = "系统鉴权失败";

    @ApiModelProperty(value = "异常消息内容")
    private String msg;

    public ZwzAuthException(String msg){
        super(msg);
        this.msg = msg;
    }

    public ZwzAuthException(){
        super(DEFAULT_MSG);
        this.msg = DEFAULT_MSG;
    }

    public ZwzAuthException(String msg, Throwable t) {
        super(msg, t);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

4.3 判断账户是否被禁用

管理系统一般支持账户禁用功能,即把 status 值设定为某个状态,如 -1

接着判断是否是账户禁用异常 DisabledException如果属于账户禁用,则给与提示,代码如下。

if (exception instanceof DisabledException) {
    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
@Component
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private ZwzLoginProperties tokenProperties;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final String LOGIN_FAIL_TIMES_PRE = "LOGIN_FAIL_TIMES_PRE:";

    private static final String REQUEST_PARAMETER_USERNAME = "username:";

    private static final boolean RESPONSE_FAIL_FLAG = false;

    private static final int RESPONSE_FAIL_CODE = 500;

    @ApiOperation(value = "查询登录失败的次数")
    public boolean recordLoginTime(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);
            return false;
        }
        return true;
    }

    @Override
    @ApiOperation(value = "登录失败回调")
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
        if (exception instanceof BadCredentialsException || exception instanceof UsernameNotFoundException) {
            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 + "次"));
            } else if(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,"账号密码不正确"));
            }
        } else if (exception instanceof ZwzAuthException){
            ResponseUtil.out(response, ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_FAIL_CODE,((ZwzAuthException) exception).getMsg()));
        } else if (exception instanceof DisabledException) {
            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 = "自定义权限过滤")
@Slf4j
public class JwtTokenOncePerRequestFilter extends OncePerRequestFilter {

    private SecurityUtil securityUtil;

    @Autowired
    private RedisTemplateHelper redisTemplate;

    private ZwzLoginProperties zwzLoginProperties;

    private static final boolean RESPONSE_FAIL_FLAG = false;

    private static final int RESPONSE_NO_ROLE_CODE = 401;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, 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 = "判断登录是否失效")
    private UsernamePasswordAuthenticationToken getUsernamePasswordAuthenticationToken(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,"登录状态失效,需要重登!"));
            return null;
        }

        TokenUser tokenUser = JSONObject.parseObject(tokenInRedis,TokenUser.class);
        userName = tokenUser.getUsername();
        List<GrantedAuthority> permissionList = new ArrayList<>();
        if(zwzLoginProperties.getSaveRoleFlag()){
            for(String permission : tokenUser.getPermissions()){
                permissionList.add(new SimpleGrantedAuthority(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 = new User(userName, "", permissionList);
            return new UsernamePasswordAuthenticationToken(user, null, permissionList);
        }
        return null;
    }

    public JwtTokenOncePerRequestFilter(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
@EnableMethodSecurity
public class WebSecurityConfig {
}

接着创建 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
@EnableMethodSecurity
public class WebSecurityConfig {

    @Autowired
    private ZwzLoginProperties zwzLoginProperties;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private AuthenticationFailHandler authenticationFailHandler;

    @Autowired
    private ZwzAccessDeniedHandler zwzAccessDeniedHandler;

    @Autowired
    private ImageValidateFilter imageValidateFilter;

    @Autowired
    private RedisTemplateHelper redisTemplate;

    @Autowired
    private SecurityUtil securityUtil;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        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();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> userDetailsService.loadUserByUsername(username);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public JwtTokenOncePerRequestFilter authenticationJwtTokenFilter() throws Exception {
        return new JwtTokenOncePerRequestFilter(redisTemplate, securityUtil, zwzLoginProperties);
    }
}

6.2 测试

请同学们运行项目后端,首先使用浏览器访问 http://localhost:8081/,系统自动重定向到登录提示页面,如下图所示。

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

接着访问免登接口 http://localhost:8081/zwz/dictData/getByType/sex,发现可以正常读取数据。

SpringBoot3整合SpringSecurity,实现自定义接口权限过滤
最终,本文成功将 SpringBoot3 整合了 SpringSecurity,实现了自定义接口权限过滤。

SpringBoot3整合SpringSecurity,实现自定义接口权限过滤文章来源地址https://www.toymoban.com/news/detail-478036.html

到了这里,关于SpringBoot3整合SpringSecurity,实现自定义接口权限过滤的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Springboot3.X整合Dubbo3.XSpringCloudAlibaba微服务 2022.0 + Springboot3.X 集成 Dubbo实现对外调用http内部调用RPC

    近期自己新开了一套SpringCloud Alibaba微服务项目,接口使用了对外HTTP,内部RPC的设计,具体点说就是外部用户或客户端通过Nginx访问到Gateway网关再分发到各个服务,内部各个服务之间统一使用Dubbo RPC进行通信。下面是Springboot3.x集成Dubbo的分享: 1. 需要的关键依赖 2. 启动程序入

    2024年02月15日
    浏览(26)
  • springboot3整合elasticsearch8.7.0实现为bean对象创建索引添加映射

    目录 准备工作 添加相关依赖 在yml中配置elasticsearch 主要内容 实体类 ElasticSearch配置类 测试 确认当前没有counter索引 启动spring 再次查询counter索引​ 在测试类中输出counter索引的映射 官方文档 要注意版本对应关系 spring官方文档中有版本对照表 目前我使用的都是最新的版本,

    2024年02月03日
    浏览(50)
  • 【Spring实战项目】SpringBoot3整合WebSocket+拦截器实现登录验证!从原理到实战

    🎉🎉 欢迎光临,终于等到你啦 🎉🎉 🏅我是 苏泽 ,一位对技术充满热情的探索者和分享者。🚀🚀 🌟持续更新的专栏 《Spring 狂野之旅:从入门到入魔》 🚀 本专栏带你从Spring入门到入魔   这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://suzee.blog.

    2024年04月17日
    浏览(40)
  • SpringBoot3.0 + SpringSecurity6.0 +OAuth2 使用Github作为授权登录

    1.1 OAuth是什么 开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。 OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供

    2024年02月11日
    浏览(30)
  • springboot3使用自定义注解+AOP+redis优雅实现防重复提交

      ⛰️个人主页:     蒾酒 🔥 系列专栏 :《spring boot实战》 🌊 山高路远,行路漫漫,终有归途 目录 写在前面 实现思路 实现步骤 1.定义防重复提交注解 2.编写一个切面去发现该注解然后执行防重复提交逻辑 3.测试 依赖条件 1.接口上标记防重复提交注解 2.接口测试 写在最

    2024年04月11日
    浏览(32)
  • 最新版 !快速掌握 JDK17 + springboot3 + springcloud Alibaba : 10、Seata 整合实现分布式事务

    上一节成功启动了seata,传送门: https://blog.csdn.net/qq_16089135/article/details/133989446 1.1 官方文档 中文文档 Seata 是什么 1.2 模式分类 AT :基于支持本地 ACID 事务的关系型数据库。 Java 应用,通过 JDBC 访问数据库。 整体机制:二阶段提交。 一阶段:业务数据和回滚日志记录在同一

    2024年02月06日
    浏览(42)
  • SpringBoot3整合MyBatisPlus

    随着 SpringBoot3 的发布, mybatisplus 也在不断更新以适配 spirngboot3 。目前仍然处于维护升级阶段,最初 2023.08 时,官方宣布对 SpringBoot3 的原生支持,详情看这里。 但是对于较新版本的 SpringBoot3 ,仍然有很多 bug ,甚至无法启动,摸爬滚打又游历社区后,实践后得到一套成功的

    2024年01月24日
    浏览(36)
  • SpringBoot3整合MinIO

    配套视频:SpringBoot3整合MinIO MinIO项目应用可参考视频 【SpringBoot3+Vue3+Uniapp小程序项目实战】 MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器

    2024年02月21日
    浏览(42)
  • SpringBoot3 整合Prometheus + Grafana

    通过Prometheus + Grafana对线上应用进行观测、监控、预警… 健康状况【组件状态、存活状态】Health 运行指标【cpu、内存、垃圾回收、吞吐量、响应成功率…】Metrics … 1. 基本使用 1. 场景引入 2. 暴露指标 3. 访问数据 访问 http://localhost:8080/actuator;展示出所有可以用的监控端点

    2024年02月14日
    浏览(31)
  • Springboot3 整合 Mybatis3

    mybatis 的必要依赖 注意:使用 springboot3 的话要使用 mybatis3 的版本以及 java17及以上的版本 注意: 如果接口上面有 注解 @Mapper 的话,就可以不用在使用扫描包注解 @MapperScan 了(当然两个可以同时存在) 注意: 最好要加上 @Mapper注解,防止忘记开启扫描 注意:头文件这里的网

    2024年01月25日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包