SpringSecurity+jwt实现权限认证功能

系列文章目录

spring security+jwt安全方案


文章目录

前言

前面我们已经通过使用springboot框架获得了管理数据的基本能力,但是一个系统不和或缺的功能是安全登录。
这里我们以springsecurity+jwt方案实现登录以及权限控制。


一、springsecurity+jwt方案

提示:这里是对该方案的原理简介
一个安全的系统是需要对请求身份进行认证的。

但是http协议是无状态的,所以需要对每次的请求进行校验。

以下是jwt方案流程图,我们以现实生活为例。当我们被一个学校录取,我们在开学的时候需要提供身份证(类比账号密码),学校就会发放一个学生证(类比jwt令牌),这样我们每次进学校带学生证就行了(每次使用系统带jwt就行了)
在这里插入图片描述

二、权限控制RBAC

提示:这里是对登录的细化,即权限功能
登录系统的人并不只是一个人,以下为RBAC的数据库设计图。

我们依然以现实世界为例,一个教务系统,有很多用户(user);其中有两种身份(role):老师和学生;老师和学生拥有不一样的功能(menu),老师可以改卷子打分等等。
在这里插入图片描述

三、实现

我们以该图为例,该流程即为需要实现的。
在这里插入图片描述

1.RBAC数据库实现

在这里插入图片描述
这里请自行搜索RBAC的sql代码

2.拦截器实现

在这里插入图片描述
给系统套上一层拦截功能即是security实现的功能,这里先实现接口放行
在maven添加以下依赖后:

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

系统会自动生成一个登录页面,我们需要做的就是给登录接口放行,其他接口拦截的配置
参考以下配置

package com.nie.sportserver.config;

import com.nie.sportserver.Interceptor.JwtTokenAdminInterceptor;
import com.nie.sportserver.exception.MyAccessDeniedHandler;
import com.nie.sportserver.exception.MyAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;

import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.time.Duration;
import java.util.Arrays;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    MyAccessDeniedHandler myAccessDeniedHandler;
    @Autowired
    MyAuthenticationEntryPoint myAuthenticationEntryPoint;
    @Autowired
    JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
    //加密算法
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //security配置跨域
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOriginPattern("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }

    //配置安全拦截
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()//关闭csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //不通过Session获取Securitycontext
                .and()//配置异常处理
                .exceptionHandling()
                .authenticationEntryPoint(myAuthenticationEntryPoint)
                .accessDeniedHandler(myAccessDeniedHandler)
                .and()
                .authorizeRequests()
                //接口匿名访问
                .antMatchers("/doc.html",
                        "/favicon.ico",
                        "/v2/api-docs",
                        "/swagger-resources/**",
                        "/webjars/**","/user/login").anonymous()//携带token了就无法访问了
                .anyRequest().authenticated();
        http.addFilterBefore(jwtTokenAdminInterceptor, UsernamePasswordAuthenticationFilter.class);
    }

    //暴露认证方法变为bean对象
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

关键在于以下代码

                .antMatchers("/doc.html",
                        "/favicon.ico",
                        "/v2/api-docs",
                        "/swagger-resources/**",
                        "/webjars/**","/user/login").anonymous()

3.登录接口实现

在这里插入图片描述
由于之前已经实现了放行,我们只需要完成查询数据库,并且将数据生成jwt即可
在上面的配置中,我们已经把认证方法暴露为bean对象,我们实现该方法即可

    //暴露认证方法变为bean对象
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

示例如下

package com.nie.sportserver.service.impl;


import com.nie.sportpojo.entity.LoginUser;

import com.nie.sportpojo.entity.User;
import com.nie.sportserver.mapper.LoginMapper;
import com.nie.sportserver.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private LoginMapper loginMapper;
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        User user = loginMapper.getByUserName(username);

        //把数据封装为UserDetail返回
        //todo 查询对应的权限信息
        List<String> list = new ArrayList<>(userMapper.selectPermsByUserId(user.getId()));
        LoginUser loginUser = new LoginUser(user,list);
        return loginUser;
    }
}

4.拦截器实现

在这里插入图片描述
在前面的配置中,我们已经将普通请求拦截了,并且使用拦截器

    @Autowired
    JwtTokenAdminInterceptor jwtTokenAdminInterceptor;

这里来实现拦截器

package com.nie.sportserver.Interceptor;



import com.nie.sportcommon.utills.JwtUtil;
import com.nie.sportpojo.entity.LoginUser;

import com.nie.sportpojo.entity.User;
import com.nie.sportserver.mapper.LoginMapper;
import com.nie.sportserver.mapper.UserMapper;
import com.nie.sportserver.properties.JwtProperties;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Component
@Slf4j
public class JwtTokenAdminInterceptor extends OncePerRequestFilter {
    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private LoginMapper loginMapper;
    @Autowired
    private UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        //从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());
        if (!StringUtils.hasText((token))) {
            filterChain.doFilter(request, response);
            return;
        }
        //校验令牌
        Long userId;
        try {
            log.info("jwt校验{}", token);
            //token解析
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            userId = Long.valueOf(claims.get("userId").toString());
            log.info("当前用户id:{}", userId);
        } catch (Exception ex) {
            throw new RuntimeException("token非法");
        }
        User user = loginMapper.getByUserId(userId);
        List<String> list = new ArrayList<>(userMapper.selectPermsByUserId(user.getId()));
        LoginUser loginUser = new LoginUser(user,list);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

其他工具类

jwt依赖

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

jwt工具类

package com.nie.sportcommon.utills;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;

public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

}

权限

权限认证使用 @PreAuthorize(“hasAuthority(‘’)”)注解

总结

本文对jwt登录校验,权限管理的原理简单描述,并且提供了实现方案

<think>好的,我需要处理用户关于Spring Security与JWT实现权限认证的面试题请求。首先,用户提到了三个引用内容,我需要仔细阅读这些引用,确保答案中的信息能够正确引用这些来源。 根据引用[1],里面提到了RBAC的权限配置、Spring Security的基本使用,以及结合JWT实现后端分离,包括JSON登录和密码加密。引用[2]和[3]则涉及具体的配置类,如继承WebSecurityConfigurerAdapter,以及JWT的加密方式和刷新令牌机制。特别是引用[3]提到了refreshToken解决令牌续期问题,还有加密方式的问题。 接下来,我需要生成相关的面试题。用户可能希望涵盖基础概念、配置步骤、核心组件、常见问题及解决方案等方面。需要确保问题覆盖Spring Security与JWT的整合流程、安全配置、权限管理、令牌刷新机制、加密方法等关键点。 然后,考虑问题的结构。通常面试题会分为基础概念、配置与整合、权限控制、安全机制、优化与扩展等部分。每个部分下需要设计具体的问题,确保问题有层次,从浅入深。 例如,基础概念部分可以问JWT的结构和优势,Spring Security的作用。配置与整合部分涉及如何配置Spring Security与JWT,核心配置类是什么。权限控制会涉及RBAC模型和注解的使用。安全机制可能包括密码加密、CSRF保护、令牌刷新。优化部分可能涉及性能优化和分布式系统的处理。 同时,要确保答案中的引用正确,比如提到令牌刷新机制时要引用[3],密码加密引用[1],配置类引用[2]。还要注意使用中文回答,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能公式较少,但需要检查是否有数学表达式的情况。 最后,生成相关问题时要覆盖用户可能关心的各个方面,帮助用户全面准备面试。需要确保问题数量适中,涵盖主要知识点,并且问题之间有逻辑连贯性。</think>### Spring Security与JWT实现权限认证面试题解析 #### 1. **基础概念** **Q1: JWT的结构是什么?它在认证中有什么优势?** A1: JWT由三部分组成:头部(Header)、载荷(Payload)、签名(Signature),格式为`Header.Payload.Signature`。优势包括: - **无状态**:服务端无需存储会话信息[^3]; - **跨域支持**:适合前后端分离架构[^1]; - **自包含性**:载荷可存储用户基本信息(如角色、权限)。 **Q2: Spring Security的核心功能是什么?** A2: Spring Security提供身份验证(Authentication)和授权(Authorization)功能,支持RBAC(基于角色的访问控制)模型[^1],可拦截请求并验证用户权限。 --- #### 2. **配置与整合** **Q3: 如何配置Spring Security与JWT整合?** A3: 关键步骤包括: 1. **继承`WebSecurityConfigurerAdapter`**:覆盖`configure`方法,定义安全规则和过滤器链[^2]。 2. **自定义`JwtAuthenticationFilter`**:拦截请求并解析JWT令牌。 3. **配置密码加密**:使用`BCryptPasswordEncoder`[^1]。 4. **禁用Session**:通过`sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)`实现无状态。 **Q4: Spring Security中`UserDetailsService`的作用是什么?** A4: 该接口用于加载用户信息(如用户名、密码、权限),需实现`loadUserByUsername`方法,从数据库或其他存储中查询用户数据[^1]。 --- #### 3. **权限控制** **Q5: 如何实现基于RBAC的权限控制?** A5: - **角色表设计**:用户表、角色表、权限表通过中间表关联[^1]。 - **注解控制**:使用`@PreAuthorize("hasRole('ADMIN')")`在方法或接口上声明权限。 - **动态配置**:通过`HttpSecurity`的`antMatchers()`配置URL路径权限[^2]。 **Q6: 如何实现接口级别的权限校验?** A6: 结合`@PreAuthorize`注解和SpEL表达式,例如: ```java @PostMapping("/admin") @PreAuthorize("hasAuthority('ADMIN_WRITE')") public void adminOperation() { ... } ``` --- #### 4. **安全机制与优化** **Q7: JWT如何解决令牌过期问题?** A7: 使用**刷新令牌(Refresh Token)**机制: - 当JWT即将过期时,客户端用Refresh Token请求新JWT。 - Refresh Token有效期较长,但需存储于服务端或加密签名。 **Q8: JWT的签名算法有哪些?如何防止令牌篡改?** A8: 常用算法包括HMAC(如HS256)和RSA(如RS256)。签名通过密钥生成,验证时重新计算签名以检测篡改。 **Q9: 如何防御CSRF攻击?** A9: Spring Security默认启用CSRF保护,但若使用JWT的无状态架构,需显式禁用: ```java @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); } ``` --- #### 5. **常见问题与扩展** **Q10: 分布式系统中JWT如何实现注销?** A10: 传统JWT无法直接注销,可通过以下方案: - **短期令牌**:缩短JWT有效期,依赖Refresh Token续期。 - **黑名单**:服务端维护失效令牌列表,但需额外存储。 **Q11: Spring Security如何整合OAuth2?** A11: 使用`spring-security-oauth2`库,配置授权服务器(`AuthorizationServerConfigurer`)和资源服务器(`ResourceServerConfigurer`),支持OAuth2协议与JWT结合。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值