SpringSecurity认证与授权(以限制登录访问视图为例)

以下类名尽量做到见名知意,如Congif的类一般放到config文件夹下,类似将不再赘述

目标

"/api/auth/login",  
"/api/auth/register",  
"/api/auth/sendCode" 

这三个接口无需身份认证(因为依赖这三个接口登录,发放jwtToken)
其余所有接口皆使用jwt认证。否则返回401

1.添加Maven依赖坐标

<!--添加 Spring Security 依赖-->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-security</artifactId>  
</dependency>

2.配置application.yaml

关闭自带的传统用户名和密码认证系统

spring:
    security:  
      user:  
        name: disabled  
        password: disabled

在本文中,认证是通过自己编写的接口与jwt实现认证,没有用到它自带的认证系统。所以关闭。否则自带的认证系统会干扰我自身的认证逻辑

3.配置config

@Configuration  
// 排除 Spring Security 默认的用户自动配置,避免创建 InMemoryUserDetailsManager// 这是因为我们使用 JWT 无状态认证,不需要默认的内存用户  
public class SecurityConfig {  
  
    private final JwtAuthenticationFilter jwtAuthenticationFilter;  
  
    //编写构造方法,让Spring识别到需要JwtAuthenticationFilter对象时自动注入进来  
    public SecurityConfig(  
            JwtAuthenticationFilter jwtAuthenticationFilter  
    ){  
        this.jwtAuthenticationFilter =  
                jwtAuthenticationFilter;  
  
    }  
  
    /*这是一个Spring Security Filter Chain(Spring Security 过滤器链)  
    * 它通过 Servlet Filter 机制,在请求到达 Controller 之前拦截 HTTP 请求,然后判断请求是否允许继续执行。*/  
    @Bean  
    public SecurityFilterChain securityFilterChain(  
            HttpSecurity http  
    ) throws Exception {  
        //关闭csrf  
        http.csrf(AbstractHttpConfigurer::disable)  
                //JWT是无状态认证,不需要Session保存用户。关闭session  
                .sessionManagement(session ->  
                        session.sessionCreationPolicy(  
                                SessionCreationPolicy.STATELESS  
                        )  
                )  
                //配置接口权限  
                .authorizeHttpRequests(  
                        auth -> auth  
                                //登录注册不需要JWT  
                                //匹配相应的http请求  
                                .requestMatchers(  
                                        "/api/auth/login",  
                                        "/api/auth/register",  
                                        "/api/auth/sendCode"  
                                )  
                                // 允许所有人访问  
                                .permitAll()  
  
                                //其他接口需要认证  
  
                                .anyRequest() // 前面没有匹配到的所有请求。  
                                .authenticated() // 要求当前请求已经通过身份认证。  
                )  
  
  
                //添加JWT过滤器  
                .addFilterBefore(  
                        // 让jwtAuthenticationFilter这个自定义过滤器在UsernamePasswordAuthenticationFilter之前执行  
                        jwtAuthenticationFilter,  
                        // 它主要用于处理传统的:用户名+密码的登录方式  
                        UsernamePasswordAuthenticationFilter.class  
                );  
  
        return http.build();  
    }  
}

4.配置过滤器

@Slf4j  
@Component  
public class JwtAuthenticationFilter  
        extends OncePerRequestFilter {  
  
    // 此处是为了使用jwt相关功能  
    private final JwtUtil jwtUtil;  
  
    // 此处是为了使用redis相关功能,用Redis做token黑名单  
    @Autowired  
    RedisStringServer redisStringServer;  
  
    // 编写构造方法,构造器注入。为了让Spring自动注入JwtUtil。可以替换为Autowired,但构造器注入的好处是final 保证依赖不会被修改  
    public JwtAuthenticationFilter(  
            JwtUtil jwtUtil  
    ){  
        this.jwtUtil = jwtUtil;  
  
    }  
  
    // 该方法的作用是实现授权功能  
    @Override  
    protected void doFilterInternal(  
            HttpServletRequest request,  
            HttpServletResponse response,  
            FilterChain filterChain  
    ) throws ServletException, IOException {  
        // 获取Authorization请求头,其值中包含Jwt的token  
        String header = request.getHeader("Authorization");  
  
        // 判断jwtToken的合法性  
        if(header != null  
                && header.startsWith("Bearer ")  
                && SecurityContextHolder  //true:当前请求还没有建立认证身份,可以继续执行 JWT 认证。false:当前请求已经有认证信息了,不需要再次通过 JWT 认证。  
                .getContext()  
                .getAuthentication() == null) {  
  
            String token = header.substring(7);  
  
            // 判断是否在黑名单中  
            boolean inBlackList = redisStringServer.existKey("jwt:blacklist:"+token);  
            if (inBlackList){  
                // 设置返回值为401  
                response.setStatus(  
                        HttpServletResponse.SC_UNAUTHORIZED  
                );  
                //如果在黑名单中则直接返回。无需解析  
                return;  
            }  
  
  
            try {  
                //解析JWT  
                String email = jwtUtil.parse(token);  
                //创建认证对象  
                UsernamePasswordAuthenticationToken authentication =  
                        new UsernamePasswordAuthenticationToken(  
                                email,//标识用户是谁  
                                null,//用户凭证,一般设为null  
  
                                Collections.emptyList()//为该用户授予的角色。因为该文章的功能不需要分角色,所以传入了空的列表  
                        );  
  
                //保存用户信息  
                SecurityContextHolder  
                        .getContext()  
                        .setAuthentication(authentication);  
  
                // 日志记录当前状态(非必须添加)  
                log.info(  
                        "认证状态:" + authentication.isAuthenticated()  
                );  
  
                log.info(  
                        "当前用户:" + authentication.getPrincipal()  
                );  
  
                log.info(  
                        "当前权限:" + authentication.getAuthorities()  
                );  
            }  
            catch(Exception e){  
                log.error("Jwt鉴权报错:",e);  
                  
                //设置返回值  
                response.setStatus(  
                        HttpServletResponse.SC_UNAUTHORIZED  
                );  
                return;  
            }  
        }  
  
  
        // 继续执行请求  
        // 把当前请求继续交给过滤器链中后面的过滤器处理。  
        filterChain.doFilter(  
                request,  
                response  
        );  
    }  
}

至此,已经基本实现了认证和授权的基本操作。下面在附录中给出用到的工具类作为补充。但不是作为主要逻辑部分

附录

jwtUtil.java

@Slf4j  
@Component  
public class JwtUtil {  
    // 设置秘钥  
    @Value("${jwt.secret}")  
    private String SECRET;  
  
    @Value("${jwt.expiration}")  
    private int EXPRIATION;  
  
    // 转换秘钥类型  
    private SecretKey SECRET_KEY;  
  
    //使用此注解可以使SpringBoot在Bean创建完成后执行该方法  
    @PostConstruct  
    private void init(){  
        SECRET_KEY = Keys.hmacShaKeyFor(  
                SECRET.getBytes(StandardCharsets.UTF_8)  
        );  
    }  
  
    /**  
     * 根据用户邮箱 ,生成唯一token  
     * @param user_email 用户邮箱  
     * @return 类型为String的token  
     */    public String generate(String user_email){  
        log.info("生成jwt,用户邮箱为:"+user_email);  
        String token = Jwts.builder()  
                //设置主题,一般以主键作为主题,因为唯一  
                .subject(user_email)  
  
                //设置过期时间  
                .expiration(new Date(  
                        System.currentTimeMillis()+EXPRIATION))  
                // 将Jwt签名。因为Jwt不是加密,而是签名  
                .signWith(SECRET_KEY)  
                // 编码生成最终数据  
                .compact();  
        System.out.println(token);  
        return token;  
    }  
  
    /**  
     * 验证JwtToken  
     * @param token 前端保存的token  
     * @return 根据token解析出来的用户邮箱(String)  
     */    public String parse(String token){  
        // 使用 JWT 解析器解析传入的 token        // token 一般是前端请求携带的 Authorization: Bearer xxx 中的 xxx 部分  
        Claims claims =  
                Jwts.parser()  
  
                        // 设置 JWT 验证使用的密钥  
                        // 解析 JWT 时,会使用这个 SECRET_KEY 对 token 的签名部分进行校验  
                        // 如果签名不正确,说明 token 被篡改,会抛出异常  
                        .verifyWith(SECRET_KEY)  
  
                        // 构建 JWT 解析器对象  
                        // 此时解析器已经知道如何验证 token 的签名  
                        .build()  
  
                        // 解析签名后的 JWT                        // 会执行:  
                        // 1. Base64 解码 Header                        // 2. Base64 解码 Payload                        // 3. 使用 SECRET_KEY 验证 Signature                        // 4. 检查 token 是否过期  
                        .parseSignedClaims(token)  
  
                        // 获取 JWT 的 Payload 部分  
                        // 返回的是 Claims 对象,本质上就是一个 Map                        // 里面保存了 subject、expiration、自定义字段等信息  
                        .getPayload();  
  
  
        // 获取 Payload 中的 subject 字段  
        // 你生成 JWT 时:  
        // .subject(user.getUser_email())  
        //        // 所以这里获取的是用户邮箱  
        // 例如:  
        // {  
        //    "sub": "12345678@qq.com",        //    "exp": 1750000000        // }        //        // 返回:  
        // "12345678@qq.com"  
        String subject = claims.getSubject();  
        System.out.println("验证后得到的主题:"+subject);  
        return subject;  
    }  
  
    /**  
     * 根据token得到token的过期时间  
     * @param token jwtToken  
     * @return 过期时间的毫秒时间戳。单位是毫秒  
     */  
    public long getExpiration(String token) {  
        return Jwts.parser()  
                .verifyWith(SECRET_KEY)  
                .build()  
                .parseSignedClaims(token)  
                .getPayload()  
                .getExpiration()  
                .getTime();  
    }  
}

RedisStringServer.java

@Service  
public class RedisStringServer implements RedisStringServerImpl {  
    @Autowired  
    private StringRedisTemplate stringRedisTemplate;  
  
    @Override  
    public void set(String key, String value, Duration ttl) {  
        stringRedisTemplate.opsForValue().set(key, value,ttl);  
    }  
  
    @Override  
    public String get(String key) {  
        return stringRedisTemplate.opsForValue().get(key);  
    }  
  
    @Override  
    public void del(String key) {  
        stringRedisTemplate.delete(key);  
    }  
  
    @Override  
    public boolean existKey(String key) {  
        return stringRedisTemplate.hasKey(key);  
    }  
  
}z
posted @ 2026-07-24 18:35  畅畅c  阅读(6)  评论(0)    收藏  举报