当前位置: 首页 > news >正文

Spring Security+JWT (5)

JWT Token

JWT字符串,字符之间通过"."分隔符分为三个子串。 每一个子串表示了一个功能块,总共有以下三个部分:JWT 头、有效载荷和签名。

头部:JWT 头部分是一个描述 JWT 元数据的 JSON 对象,主要设置一些规范信息,签名部分的编码格式就在头部中声明。

载荷:token中存放有效信息的部分,是 JWT 的主体内容部分,是一个 JSON 对象,包含需要传递的数据。比如用户名,用户角色,过期时间等,但是不要放密码,会泄露。

指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID 用于标识该 JWT

1. 基本流程

        认证:通过自定义一个用户名和密码登录的过滤器JwtTokenLoginFilter,登录成功后生成JWT并返回。客户端调用接口需要传JWT Token,通过过滤器AuthJwtTokenFilter对请求拦截并验证Token的合法性,已经是否过期。

        授权:通过使用注解(类似@PreAuthorize)或者配置(.antMatchers("/hellouser").hasAuthority("query"))对接口配置权限。authenticationEntryPoint是配置认证异常,accessDeniedHandler是配置权限异常

        

2.JWT工具类

@Component
@Data
public class JwtUtils {

    //秘钥
    private String secret = "mysecret";

    // 过期时间 毫秒
    private Long expiration = 120l * 1000;


    public String createToken(String userName) {
        return Jwts.builder()
                .setSubject(userName)
                //生成时间
                .setIssuedAt(new Date())
                //过期时间
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String generateToken(Map<String, Object> claims, Long expiration) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
    }


    /**
     * 从token中解析出数据
     *
     * @param token 令牌
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            throw e;
        }
        return claims;
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            throw e;
        }
        return username;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put(Claims.ISSUED_AT, new Date());
            refreshedToken = generateToken(claims, 2 * expiration);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌
     *
     * @param token         令牌
     * @param userInputName 用户
     * @return 是否有效
     */
    public Boolean validateToken(String token, String userInputName) {
        String username = getUsernameFromToken(token);
        return (username.equals(userInputName) && !isTokenExpired(token));
    }
}

2.登录过滤器

public class JwtTokenLoginFilter extends AbstractAuthenticationProcessingFilter {

    public JwtTokenLoginFilter(String filterProcessesUrl) {
        super(new AntPathRequestMatcher(filterProcessesUrl, HttpMethod.POST.name()));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //获取表单提交数据
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //封装到token中提交
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username,password);
        return getAuthenticationManager().authenticate(authRequest);
    }
}

3.认证过滤器

public class AuthJwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    MyUserDetailsService myUserDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        /**
         * token存在则校验token
         * 1. token是否存在
         * 2. token存在:
         *  2.1 校验token中的用户名是否失效
         */
        String username = "";
        if (!StringUtils.isEmpty(token)) {
            try {
                username = jwtUtils.getUsernameFromToken(token);
                //SecurityContextHolder.getContext().getAuthentication()==null 未认证则为true
                if (!StringUtils.isEmpty(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
                    //如果token有效
                    if (jwtUtils.validateToken(token, userDetails.getUsername())) {
                        // 将用户信息存入 authentication,方便后续校验
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
                                userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        // 将 authentication 存入 ThreadLocal,方便后续获取用户信息
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            } catch (ExpiredJwtException e) {
                response.setStatus(HttpStatus.FORBIDDEN.value());
                ResponseUtils.result(response, "token已过期,重新登录!");
                return;
            }
        }
        //继续执行下一个过滤器
        chain.doFilter(request, response);
    }
}

4.实现UserDetailsService

@Slf4j
public class MyUserDetailsService implements UserDetailsService {

    Map<String, String> user = new HashMap();
    Map<String, List<String>> userRole = new HashMap<>();

    {
        user.put("admin", "111111");
        user.put("zs", "111111");
        user.put("noaccess", "111111");

        //hasRole 的处理逻辑和 hasAuthority 似乎是一样的,只是hasRole 这
        // 里会自动给传入的字符串前缀(默认是ROLE_ ),
        // 使用 hasAuthority 更具有一致性,不用考虑要不要加 ROLE_ 前缀,
        // 在UserDetailsService类的loadUserByUsername中查询权限,也不需要手动增加。
        // 在SecurityExpressionRoot 类中hasAuthority 和 hasRole
        // 最终都是调用了 hasAnyAuthorityName 方法。
        userRole.put("admin", Arrays.asList("ROLE_admin"));
        userRole.put("zs", Arrays.asList("query"));
    }

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("MyUserDetailsService.loadUserByUsername 开始");
        if (!user.containsKey(username)) {
            throw new UsernameNotFoundException("username is not exists");
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        if (userRole.containsKey(username)) {
            authorities = userRole.get(username).stream().map(row -> new SimpleGrantedAuthority(row))
                    .collect(Collectors.toList());
        }
        return new User(username, passwordEncoder.encode(user.get(username)), authorities);
    }
}

5.登录成功和失败处理的Handler

@Component
public class LoginAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        if (exception instanceof BadCredentialsException) {
            ResponseUtils.result(response, "用户名或密码不正确!");
        }
        ResponseUtils.result(response, "登录失败");
    }
}
@Component
public class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private JwtUtils jwtTokenUtil;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //生成令牌
        String accessToken = jwtTokenUtil.createToken(userDetails.getUsername());
        //生成刷新令牌,如果accessToken令牌失效,则使用refreshToken重新获取令牌(refreshToken过期时间必须大于accessToken)
        String refreshToken = jwtTokenUtil.refreshToken(accessToken);

        Map<String, Object> resultData = new HashMap<>();
        Map<String, Object> tokenData = new HashMap<>();
        tokenData.put("accessToken", accessToken);
        tokenData.put("refreshToken", refreshToken);
        resultData.put("msg", "登录成功");
        resultData.put("token", tokenData);
        ResponseUtils.result(response, resultData);
    }
}

 6.Security配置类

@Configuration
public class JwtTokenLoginConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    MyUserDetailsService myUserDetailsService;

    @Autowired
    LoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler;

    @Autowired
    LoginAuthenticationFailureHandler loginAuthenticationFailureHandler;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        JwtTokenLoginFilter filter = new JwtTokenLoginFilter("/mylogin");
        filter.setAuthenticationManager(httpSecurity.getSharedObject(AuthenticationManager.class));

        //认证成功处理器
        filter.setAuthenticationSuccessHandler(loginAuthenticationSuccessHandler);
        //认证失败处理器
        filter.setAuthenticationFailureHandler(loginAuthenticationFailureHandler);

        httpSecurity.authenticationProvider(daoAuthenticationProvider());

        //将过滤器添加到UsernamePasswordAuthenticationFilter之前执行
        httpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    DaoAuthenticationProvider daoAuthenticationProvider() {
        //使用DaoAuthenticationProvider
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        //设置userDetailService
        provider.setUserDetailsService(myUserDetailsService);
        //设置加密算法
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }
}
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    JwtTokenLoginConfig jwtTokenLoginConfig;

    @Autowired
    AuthJwtTokenFilter authJwtTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭csrf
        http.csrf().disable()
                //禁用表单登录
                .formLogin().disable()
                //应用登录过滤器的配置,配置分离
                .apply(jwtTokenLoginConfig);

        http.authorizeRequests()
                .antMatchers("/index", "/refreshToken")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                //禁用session,JWT校验不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //处理异常情况:认证和权限
        http.exceptionHandling()
                //认证未通过,不允许访问异常处理器--认证异常
                .authenticationEntryPoint(MyWebSecurityConfig::MyAuthenticationEntryPoint)
                //认证通过,但是没权限处理器--授权异常
                .accessDeniedHandler(MyWebSecurityConfig::MyAccessDeniedHandler);
        http.addFilterBefore(authJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    //
    public static void MyAuthenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        ResponseUtils.result(response, "无权限访问,请先登录!");
    }

    public static void MyAccessDeniedHandler(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        ResponseUtils.result(response, "无权限!");
    }
}

7.测试Controller

· index接口可以命令访问
 
· loginstatus接口需要登录成功后才能访问,因为未设置权限,所以不需要授权就能访问。

· hello****的接口登录认证成功后,并且需要想要的权限才能访问。

授权的注解,一定要开启@EnableGlobalMethodSecurity(prePostEnabled = true)

@RestController
public class HelloController {

    @GetMapping("/index")
    public String index() {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("<div><a href='/hello'>hello</a></div>");
        stringBuffer.append("<div><a href='/helloadmin'>helloadmin</a></div>");
        stringBuffer.append("<div><a href='/hellouser'>hellouser</a></div>");
        stringBuffer.append("<div><a href='/loginstatus'>loginalert</a></div>");
        return stringBuffer.toString();
    }

    /**
     * 不需要权限,只需要认证即可访问
     * @return
     */
    @GetMapping("/loginstatus")
    public String loginalert() {
        return "已经登录成功了";
    }

    @GetMapping("/hello")
    @PreAuthorize("hasRole('admin') OR hasAuthority('query')")
    public String hello() {
        return "HelloController hello";
    }

    @GetMapping("/helloadmin")
    @PreAuthorize("hasRole('admin')")
    public String helloAdmin() {
        return "HelloController helloAdmin";
    }

    @GetMapping("/hellouser")
    @PreAuthorize("hasRole('admin') OR hasAuthority('query')")
    public String helloUser() {
        return "HelloController helloUser";
    }
}

@RestController
public class LoginController {

    @Autowired
    JwtUtils jwtUtils;

    /**
     * 刷新令牌
     *
     * @return
     */
    @PostMapping("/refreshToken")
    public ResponseEntity<Object> refreshToken(HttpServletRequest request) {
        //从请求头中获取refreshToken
        String oldRefreshToken = request.getHeader("Authorization");
        //校验refreshToken,如果令牌没有过期
        if (jwtUtils.isTokenExpired(oldRefreshToken)) {
            return new ResponseEntity<>("刷新令牌已过期,请重新登录!", HttpStatus.ACCEPTED);
        }

        //解析refreshToken
        String username = jwtUtils.getUsernameFromToken(oldRefreshToken);
        //生成新的accessToken
        String newAccessToken = jwtUtils.createToken(username);
        String newRefreshToken = jwtUtils.refreshToken(newAccessToken);

        Map<String, Object> resultData = new HashMap<>();
        Map<String, Object> tokenData = new HashMap<>();
        tokenData.put("accessToken", newAccessToken);
        tokenData.put("refreshToken", newRefreshToken);
        resultData.put("msg", "刷新令牌成功");
        resultData.put("token", tokenData);
        return ResponseEntity.ok(resultData);
    }
}

8.其他

public class ResponseUtils {
    public static void result(HttpServletResponse response, Object msg) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        ServletOutputStream out = response.getOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        out.write(objectMapper.writeValueAsString(msg).getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}
@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJwtProgram {

    public static void main(String[] args) {
        SpringApplication.run(SecurityJwtProgram.class, args);
    }

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

    @Bean
    public MyUserDetailsService myUserDetailsService() {
        return new MyUserDetailsService();
    }

    // 自定义的Jwt Token校验过滤器
    @Bean
    public AuthJwtTokenFilter authJwtTokenFilter() {
        return new AuthJwtTokenFilter();
    }
}

9.测试

在MyUserDetailsService中分别创建了三个账号,admin,zs, noaccess,所以对这三个账号分别验证。

mylogin接口:

loginstatus接口:

                

                                    

hello接口: 

 

                                ​​​​​​​        

helloadmin接口:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

hellouser接口:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​

参考:

SpringBoot+SpringSecurity+JWT实现认证和授权_springboot security jwt-CSDN博客

相关文章:

  • 红队内网攻防渗透:内网渗透之内网对抗:实战项目VPC2打靶父子域三层路由某绒免杀下载突破约束委派域控提权
  • 深度解析:大模型在多显卡服务器下的通信机制与分布式训练——以DeepSeek、Ollama和vLLM为例
  • 安全面试4
  • 谷歌浏览器更新后导致的刷新数据无法显示
  • C++标准库提供了哪些智能指针类型以及它们的区别
  • 小红书运营教程(内容笔记01)
  • 网络安全 | 信息安全管理体系(ISMS)
  • Linux文件系统----磁盘级文件
  • 《GNU/Linux Shell命令全解析》
  • CSS背景属性
  • python——GUI图形用户界面编程
  • MySQL主从服务器配置教程
  • 【C++】模板初阶和STL简介
  • Linux提权之metasploit 提权(五)
  • 登录-07.JWT令牌-登录后下发令牌
  • 编程题-连接两字母单词得到的最长回文串(中等)
  • 从网络基础到安全防护:网安运维小白的入门学习路线
  • python-静态方法和类方法
  • 蓝桥杯训练题目(一)—— 难度:简单(除了最后一题哈)
  • 《Python实战进阶》专栏 No 4:高效、简洁、强大之使用 FastAPI 构建高性能异步API
  • php公司网站/明天上海封控16个区
  • 互联网网站建设公司/百度seo点击排名优化
  • 建设网站banner/百度seo关键词排名优化工具
  • flash 做网站/网站策划书怎么写
  • 椒江网站建设578做网站/seow
  • 石家庄网站建设备案/网络营销软件条件