Spring Security 框架

引入依赖

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

基本原理

认证链

Spring Security的认证处理链是一系列的过滤器链,用于处理用户的身份验证和授权操作。这些过滤器在请求处理过程中依次执行,并在不同的阶段进行不同的认证和授权操作,以确保应用程序的安全性和完整性。

下面是Spring Security的标准认证处理链:

  1. UsernamePasswordAuthenticationFilter:该过滤器用于获取用户输入的用户名和密码,并将其封装在UsernamePasswordAuthenticationToken对象中。它还检查用户输入的凭据是否正确,并将其发送到AuthenticationManager进行身份验证。
  2. BasicAuthenticationFilter:该过滤器用于处理基本认证,即通过HTTP消息头传递的用户名和密码。在进行基本认证时,前端应用程序会将用户凭据(base64编码的)添加到HTTP请求标头中。BasicAuthenticationFilter在此通过解码这些凭据并将其发送到AuthenticationManager进行身份验证。
  3. SecurityContextHolderAwareRequestFilter:该过滤器用于包装HttpServletRequest对象,以便将其传递到Spring Security的AuthenticationAwareWebInvocationPrivilegeEvaluator。这主要是用于在页面或JavaScript中检查是否已经经过身份验证。
  4. AnonymousAuthenticationFilter:该过滤器负责提供匿名身份验证机制。如果用户未经过身份验证,则该过滤器会向SecurityContext注入一个AnonymousAuthenticationToken对象。
  5. SessionManagementFilter:该过滤器管理用户的会话,包括创建新的会话、检查会话是否过期以及将会话绑定到用户的身份验证,以防止会话劫持攻击。
  6. ExceptionTranslationFilter:该过滤器处理由其他过滤器抛出的异常。它查找最合适的异常处理程序,例如包含特定响应头和状态代码的RESTful API异常处理程序或进行页面重定向的MVC样式的异常处理程序。
  7. FilterSecurityInterceptor:该过滤器在处理请求之前检查当前用户是否具有所请求的资源的访问权限。如果用户没有权限,则FilterSecurityInterceptor将阻止请求并返回HTTP 403 Forbidden响应。

请求 —》UsernamePasswordAuthenticationFilter—》 … —》ExceptionTranslationFilter —》FilterSecurityInterceptor —》API —》响应 —》FilterSecurityInterceptor —》…(原路返回)

SecurityConfig (WebSecurityConfigurerAdapter)

package com.fanchen.config;

import com.fanchen.security.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private LoginFailHandler loginFailHandler;

    @Resource
    private LoginSuccessHandler loginSuccessHandler;

    @Resource
    private CaptchaFilter captchaFilter;

    @Resource
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Resource
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Resource
    private UserDetailServiceImpl userDetailService;

    @Resource
    private JwtLogoutSuccessHandler jwtLogoutSuccessHandler;

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        return new JwtAuthenticationFilter(authenticationManager());
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    //白名单
    private static final String[] URL_WHITELIST = {
            "/login",
            "/register",
            "/captcha",
            "/register/deptList",
            "/favicon.ico",
    };

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        http.cors().and().csrf().disable()
        //登录配置
        .formLogin()
                .successHandler(loginSuccessHandler)
                .failureHandler(loginFailHandler)
        .and()
                .logout()
                .logoutSuccessHandler(jwtLogoutSuccessHandler)
                
        //禁用Session
        .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        //配置拦截器
        //anonymous() 允许匿名用户访问,不允许已登入用户访问
        //permitAll() 不管登入,不登入 都能访问
        .and()
                .authorizeRequests()
                .antMatchers(URL_WHITELIST).anonymous()
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "//*.html",
                        "//*.css",
                        "//*.js",
                        "/img/"
                ).permitAll()
                .antMatchers("/swagger-ui.html").anonymous()
                .antMatchers("/swagger-resources/").anonymous()
                .antMatchers("/webjars/").anonymous()
                .antMatchers("/*/api-docs").anonymous()
                .antMatchers("/druid/**").anonymous()
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable()
        //异常处理器
        .and()
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler)
        //配置自定义过滤器
        .and()
        .addFilter(jwtAuthenticationFilter())
        .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
        ;
    }

    //配置userSecvice
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }

}

AccessDeniedHandler(处理访问权限不足的处理器)

@Slf4j
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        log.info("访问受限资源! 执行handle方法:");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        Result fail = Result.fail(e.getMessage());
        outputStream.write(JSONUtil.toJsonStr(fail).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

AuthenticationEntryPoint(处理访问未认证的逻辑)

commence

package com.fanchen.security;

import cn.hutool.json.JSONUtil;
import com.fanchen.common.lang.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;


/**
 * @description  认证失败的处理逻辑
 * @author Zhang Guangyun
 * @date 2023/4/23 20:23
 */
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        log.info("认证失败,执行commence方法");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        Result fail = Result.fail(401, "请先登录", null);
        outputStream.write(JSONUtil.toJsonStr(fail).getBytes(StandardCharsets.UTF_8));
        log.info("outputStream = {}",JSONUtil.toJsonStr(fail).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

AuthenticationEntryPoint 与 AccessDeniedHandler有何不同?
AuthenticationEntryPoint和AccessDeniedHandler都是Spring Security处理安全相关问题的接口,但它们处理的问题不同,有以下几个不同点:
调用时机不同:AuthenticationEntryPoint是在用户请求需要身份验证的资源而没有携带认证信息时被触发,而AccessDeniedHandler是在用户尝试访问需要授权的资源但是因为权限不足而被拒绝时被触发。
负责的任务不同:AuthenticationEntryPoint主要负责为用户提供身份验证机会,返回401 Unauthorized错误页面或重定向到登录页面等信息,使用户进行身份验证并重新尝试访问资源。而AccessDeniedHandler主要负责为用户提供授权机会,返回403 Forbidden错误页面或自定义授权错误信息等信息,使用户及时提交授权申请或提供正确的授权信息。
方法参数不同:AuthenticationEntryPoint的commence()方法将从用户请求中提取AuthenticationException,而AccessDeniedHandler的handle()方法将从请求中提取AccessDeniedException。
代码实现不同:在SecurityConfig中配置时,AuthenticationEntryPoint通常使用exceptionHandling()来配置,而AccessDeniedHandler通常使用AccessDecisionManager()来配置。

LogoutSuccessHandler(处理登出成功处理器)

package com.fanchen.security;

import cn.hutool.json.JSONUtil;
import com.fanchen.common.lang.Result;
import com.fanchen.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {

    @Resource
    private JwtUtil jwtUtil;

    /**
     * @description 登出成功的处理逻辑
     * @author Zhang Guangyun
     * @date 2023/4/23 20:30
     * @param httpServletRequest
     * @param httpServletResponse
     * @param authentication
     *  SecurityContextHolder 中清除 当前用户的认证信息(Authentication 对象)
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        log.info("正在执行  onLogoutSuccess  方法:");
        if (authentication != null){
            new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);
        }
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.setHeader(jwtUtil.getHeader(), "");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        Result success = Result.succ("退出成功");
        outputStream.write(JSONUtil.toJsonStr(success).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

AuthenticationSuccessHandler(处理登入成功逻辑)

onAuthenticationSuccess

package com.fanchen.security;

import cn.hutool.json.JSONUtil;
import com.fanchen.common.lang.Result;
import com.fanchen.utils.AsyncTaskUtil;
import com.fanchen.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * @description  Spring Security 下的 登录
 * @author Zhang Guangyun
 * @date 2023/4/23 15:50
 */
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Resource
    private JwtUtil jwtUtil;

    @Resource
    private AsyncTaskUtil asyncTaskUtil;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        log.info("正在执行 onAuthenticationSuccess 方法:");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        String jwt = jwtUtil.createToken(authentication.getName());
        log.info("jwt  =  {}",jwt);
        httpServletResponse.setHeader(jwtUtil.getHeader(), jwt);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        Result success = Result.succ("登录成功");
        log.info("**************登录成功********************");
        outputStream.write(JSONUtil.toJsonStr(success).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
        asyncTaskUtil.recordLoginInfo(authentication.getName(), 1, "登陆成功", new Date());
    }
}

AuthenticationFailureHandler(处理登录失败的逻辑)

onAuthenticationFailure

package com.fanchen.security;

import cn.hutool.json.JSONUtil;
import com.fanchen.common.lang.Result;
import com.fanchen.utils.AsyncTaskUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
public class LoginFailHandler implements AuthenticationFailureHandler {

    @Resource
    private AsyncTaskUtil asyncTaskUtil;

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        String message = e.getMessage();
        if (!"验证码错误".equals(message) && !"请刷新验证码".equals(message)){
            message = "用户名或密码错误";
        }
        Result fail = Result.fail(message);
        outputStream.write(JSONUtil.toJsonStr(fail).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
        if (message.equals("请刷新验证码")){
            return;
        }
        asyncTaskUtil.recordLoginInfo(null, 0, message, new Date());
    }
}

UserDetails

AccountUser(记录用户的特征)

记录用户的特征:用户名,密码,

package com.fanchen.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;

import java.util.Collection;

/**
 * @description UserDetails对象
 * @author Zhang Guangyun
 */
public class AccountUser implements UserDetails {

    private Long userId;

    private String password;

    private final String username;

    private final Collection<? extends GrantedAuthority> authorities;

    private final boolean accountNonExpired;

    private final boolean accountNonLocked;

    private final boolean credentialsNonExpired;

    private final boolean enabled;

    public AccountUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(userId, username, password, true, true, true, true, authorities);
    }

    /**
     * @description user 的对象(*)
     * @author Zhang Guangyun
     * @date 2023/4/23 20:20
     * @param userId
     * @param username
     * @param password
     * @param enabled
     * @param accountNonExpired
     * @param credentialsNonExpired
     * @param accountNonLocked
     * @param authorities
     */
    public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired,
                       boolean credentialsNonExpired, boolean accountNonLocked,
                       Collection<? extends GrantedAuthority> authorities) {
        Assert.isTrue(username != null && !"".equals(username) && password != null,
                "无法将null或空值传递给构造函数");
        this.userId = userId;
        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

UserDetailsService

UserDetailServiceImpl

package com.fanchen.security;

import com.fanchen.entity.SysUser;
import com.fanchen.service.SysUserService;
import com.fanchen.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

/**
 * @description
 * @author Zhang Guangyun
 * @date 2023/4/23 16:14
 * 在 Spring Security 中,UserDetailsService 接口用于加载用户信息。它是一个核心接口,用于将用户的信息从持久化存储(如数据库、LDAP 等)中加载到内存中,
 * 供 Spring Security 使用。在用户登录时,Spring Security 会使用 UserDetailsService 加载用户信息,并将其封装到 Authentication 对象中,以进行后续的身份认证和授权操作。
 */
@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Resource
    private SysUserService sysUserService;

    @Resource
    private RedisUtil redisUtil;

    @Override
    public UserDetails loadUserByUsername(String username) {
        log.info("执行loadUserByUsername方法,username = {}",username);
        SysUser sysUser = sysUserService.getByUsername(username);
        if (sysUser == null) {
            throw new UsernameNotFoundException("用户名或密码错误");
        }
        //在redis注册登录用户
        redisUtil.set("User:" + username, sysUser, 3700);

        AccountUser user = new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), null);

        return user;
    }


    /**
     * 用户权限信息(角色,菜单权限)
     * @param username 用户id
     * @return 权限列表
     */
    public List<GrantedAuthority> getUserAuthority(String username){
        String authority = sysUserService.getUserAuthority(username);
        log.info("authority = {}",authority);
        //将,分割的用户 权限 变成 list类型 返回回来
        List<GrantedAuthority> grantedAuthorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
        return grantedAuthorityList;
    }
}

BasicAuthenticationFilter

doFilterInternal
Spring Security 中的 BasicAuthenticationFilter 是一个过滤器,用于处理 HTTP 基本身份验证。在执行身份验证之前,它会检查请求中是否包含 Authorization 头,以确定是否需要进行身份验证。如果请求需要身份验证,则 BasicAuthenticationFilter 从 Authorization 头中获取凭证,并使用 Spring Security 中的 AuthenticationManager 进行身份验证。

doFilterInternal() 是 BasicAuthenticationFilter 的主要方法,它实现了 Filter 接口定义的方法,用于处理请求。在 doFilterInternal() 方法中,它首先检查请求中是否包含了 Authorization 头,如果没有 Authorization 头,那么它会检查是否已经设置了 SecurityContextHolder 中的 Authentication 对象,如果已经设置了,则直接放行,让请求继续被处理。如果没有设置 Authentication 对象,则返回一个 401 无权限响应。
如果请求中包含了 Authorization 头,则 BasicAuthenticationFilter 会解析 Authorization 头,提取出 Base64 编码的用户名和密码,并使用 AuthenticationManager 进行身份验证。如果身份验证成功,则BasicAuthenticationFilter 根据用户名和密码创建一个 UsernamePasswordAuthenticationToken 对象,并将其设置为 SecurityContextHolder 中的 Authentication 对象。然后请求被放行,并继续被其他过滤器或 Spring Security 的安全配置所处理。
如果身份验证失败,则 BasicAuthenticationFilter 将返回一个 401 无权限响应,请求不会被进一步处理。

package com.fanchen.security;

import cn.hutool.core.util.StrUtil;
import com.fanchen.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    @Resource
    private JwtUtil jwtUtil;

    @Resource
    private UserDetailServiceImpl userDetailService;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }


    /**
     *BasicAuthenticationFilter 是 Spring Security 提供的一个过滤器,用于处理 HTTP Basic 认证方式的请求。在客户端发送 HTTP 请求时,如果请求头中包含了 Authorization 字段,那么服务器端就可以通过该字段中携带的用户名和密码来进行认证。BasicAuthenticationFilter 类就是用来处理这种认证方式的。
     * BasicAuthenticationFilter 类继承自 OncePerRequestFilter 类,实现了其中的 doFilterInternal() 方法。在该方法中,它会首先判断当前请求是否包含 Authorization 请求头,如果没有,则直接调用 FilterChain 对象的 doFilter() 方法将请求传递给下一个过滤器或 servlet。如果请求头中包含了 Authorization 字段,则会对其中的用户名和密码进行解析并进行认证。具体的流程如下:
     * 获取请求头中的 Authorization 字段,并从中提取出用户名和密码。
     * 将提取出的用户名和密码封装到一个 UsernamePasswordAuthenticationToken 对象中。
     * 调用 AuthenticationManager 对象的 authenticate() 方法对该 UsernamePasswordAuthenticationToken 进行认证。
     * 如果认证成功,则将认证信息保存到 SecurityContextHolder 中,并调用 FilterChain 对象的 doFilter() 方法将请求传递给下一个过滤器或 servlet。如果认证失败,则会调用 AuthenticationEntryPoint 对象的 commence() 方法返回认证失败的响应。
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("执行doFilterInternal 方法: ");

        String jwt = request.getHeader(jwtUtil.getHeader());
        //如果jwt 是空白 或者 undefinded
        if (StrUtil.isBlankOrUndefined(jwt)){
            chain.doFilter(request, response);
            return;
        }
        //解析token
        Claims claims = jwtUtil.parserToken(jwt);

        if (claims == null){
            throw new JwtException("token异常");
        }

        if (jwtUtil.isExpire(claims)){
            throw new JwtException("token过期");
        }

        String username = claims.getSubject();
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null,
                                                             userDetailService.getUserAuthority(username));
        SecurityContextHolder.getContext().setAuthentication(token);

        chain.doFilter(request, response);
    }
}

UsernamePasswordAuthenticationToken 对象

在 Spring Security 中,UsernamePasswordAuthenticationToken 对象是一种用于简单认证的身份验证对象。Spring Security 支持各种身份验证机制,并提供了可以在这些机制中使用的通用身份验证对象。其中包括 UsernamePasswordAuthenticationToken 对象。
UsernamePasswordAuthenticationToken 对象可以用于包装用户名和密码等凭据,并将这些凭据传递给进行身份验证的 AuthenticationManager。如果身份验证成功,则 UsernamePasswordAuthenticationToken 对象将包含一个认证主体,其中包含关于已认证用户的信息,该对象将被存储在 SecurityContextHolder 中。
在大多数情况下,UsernamePasswordAuthenticationToken 对象的 credentials 属性具有密码(凭证),principal 属性具有用户名或身份识别值。在进行身份验证后,这些凭据被移除。
以下是一个示例代码,说明如何创建一个

UsernamePasswordAuthenticationToken 对象:
Authentication token = new UsernamePasswordAuthenticationToken(username, password);
Authentication result = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(result);

在该示例中,我们首先创建一个 UsernamePasswordAuthenticationToken 对象,并将用户名和密码作为参数传入构造函数。然后使用 AuthenticationManager 对象调用 authenticate() 方法来进行身份验证。如果验证成功,就会返回一个 Authentication 对象,该对象将包含有关已经验证的用户的信息。最后,使用 SecurityContextHolder.getContext().setAuthentication() 方法设置该身份验证信息,以便它可以在整个应用程序的生命周期内访问。

OncePerRequestFilter (自定义过滤器)

OncePerRequestFilter是Spring Security框架中的一个抽象类,实现了javax.servlet.Filter接口,通过继承它可以方便地实现对请求的过滤。它的作用是确保在请求处理期间只被调用一次,即只会过滤一次该请求,可以避免同一请求被重复处理的问题。

OncePerRequestFilter实现了doFilter()方法,该方法在每个请求到达过滤器时会被调用。通过继承该类并重写方法,可以自定义实现请求过滤器的逻辑。一般来说我们在该过滤器中可以对请求进行处理,例如:

  1. 鉴权:检查请求中是否有合法的认证信息,如果没有,可以拦截请求并返回错误信息或者重定向到登录页面等操作,确保资源得到保护。
  2. 日志:记录请求的详细信息,例如请求头、URL、请求参数等,方便后续的审计和追踪分析。
  3. 缓存:根据请求的不同参数将相应的结果进行缓存,减轻服务器的负担,提高性能。
    OncePerRequestFilter通常被用来实现我们自定义的请求过滤逻辑,比如对请求进行鉴权、日志记录、缓存等操作。我们可以继承该类,并重写其中的doFilterInternal方法,实现我们自己的处理逻辑。该方法会在每个请求到达过滤器时被调用,由于OncePerRequestFilter的存在,我们可以确保每个请求只被过滤一次,避免重复处理导致性能问题的发生。
package com.fanchen.security;

import com.fanchen.common.exception.CaptchaException;
import com.fanchen.common.lang.Const;
import com.fanchen.utils.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @description  验证码
 * @author Zhang Guangyun
 * @date 2023/5/27 15:54
 */
@Component
public class CaptchaFilter extends OncePerRequestFilter {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private LoginFailHandler loginFailHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String url = httpServletRequest.getRequestURI();
        if (("/login".equals(url) && httpServletRequest.getMethod().equals("POST")) || ("/register".equals(url) && httpServletRequest.getMethod().equals("POST"))){
            try {
                validate(httpServletRequest);
            }catch (CaptchaException captchaException){
                loginFailHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, captchaException);
                return;
            }
        }
        doFilter(httpServletRequest, httpServletResponse, filterChain);
    }

    private void validate(HttpServletRequest request) {
        String code = request.getParameter("code").toLowerCase();
        String key = request.getParameter("key");
        if (StringUtils.isBlank(code) || StringUtils.isBlank(key)) {
            throw new CaptchaException("验证码错误");
        }
        Object hget = redisUtil.hget(Const.CAPTCHA_KEY, key);
        if (hget == null || hget.equals("")){
            throw new CaptchaException("请刷新验证码");
        }
        if (!code.equals(hget.toString())) {
            throw new CaptchaException("验证码错误");
        }
        redisUtil.hdel(Const.CAPTCHA_KEY, key);
    }
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你们卷的我睡不着QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值