SpringBoot集成Spring security JWT实现接口权限认证

下面是详细讲解“SpringBoot集成Spring security JWT实现接口权限认证”的完整攻略。

概述

在实际项目中,对于接口权限认证一直是非常重要的问题。在 SpringBoot 中使用 Spring Security 与 JWT(JSON Web Token)完成接口权限认证是一种常见的方式。本文将介绍如何在 SpringBoot 中集成 Spring Security 和 JWT,实现接口权限认证。

前提条件

在开始之前,需要具备如下知识:

  • SpringBoot 基础知识
  • Spring Security 基础知识
  • JWT 基础知识

步骤

1. 添加相关依赖

首先需要在 pom.xml 文件中添加如下依赖:

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

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
  • spring-boot-starter-security 用于集成 Spring Security
  • jjwt 用于生成和解析 JWT

2. 实现用户认证

实现用户认证的方式有很多,本文使用内存数据库实现用户认证。在 SecurityConfig 类中配置了两个用户,一个是管理员,一个是普通用户。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        String password = passwordEncoder().encode("123456");

        auth.inMemoryAuthentication()
                .withUser("admin").password(password).roles("ADMIN")
                .and()
                .withUser("user").password(password).roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and().csrf().disable().authorizeRequests()
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER")
                .anyRequest().authenticated()
                .and().addFilter(new JwtAuthenticationFilter(authenticationManager()))
                .addFilter(new JwtAuthorizationFilter(authenticationManager()))
                .sessionManagement().sessionCreationPolicy(STATELESS);
    }

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

3. 实现 JWT 认证

通过 JWT 认证,可以实现无状态的分布式认证。在本文中实现 JWT 认证方式有两种,一种是使用 BasicAuthenticationFilter 处理,一种是使用自定义 Filter 处理。

3.1 使用 BasicAuthenticationFilter

基于 BasicAuthenticationFilter 的方式,需要在 JwtAuthenticationFilter 类中实现用户认证方法。在使用该方式的时候,需要注意在请求头中设置 Authorization 字段。

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

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

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization").replace("Bearer ", "");

        if (token != null) {
            String username = JWT.require(Algorithm.HMAC256(SECRET.getBytes()))
                    .build()
                    .verify(token)
                    .getSubject();

            if (username != null) {
                return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
            }

            return null;
        }

        return null;
    }
}

3.2 使用自定义 Filter

自定义 Filter 的方式可以多端共用,比如 web 端、移动端等。自定义 JWT 认证过滤器的代码如下:

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

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

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization").replace("Bearer ", "");

        if (token != null) {
            try {
                Jws<Claims> claimsJws = Jwts.parser()
                        .setSigningKey(SECRET.getBytes())
                        .parseClaimsJws(token);

                String username = claimsJws.getBody().getSubject();
                List<SimpleGrantedAuthority> authorities = ((List<?>) claimsJws.getBody()
                        .get("role")).stream()
                        .map(authority -> new SimpleGrantedAuthority((String) authority))
                        .collect(Collectors.toList());

                if (username != null) {
                    return new UsernamePasswordAuthenticationToken(username, null, authorities);
                }

                return null;
            } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
                throw new JwtAuthenticationException("JWT token is invalid", HttpStatus.UNAUTHORIZED);
            }

        }

        return null;
    }
}

4. 接口权限控制

SecurityConfig 类中,可以配置接口访问权限:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors()
            .and().csrf().disable().authorizeRequests()
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            .antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER")
            .anyRequest().authenticated()
            .and().addFilter(new JwtAuthenticationFilter(authenticationManager()))
            .addFilter(new JwtAuthorizationFilter(authenticationManager()))
            .sessionManagement().sessionCreationPolicy(STATELESS);
}

通过上述配置,所有以 /api/admin/ 开头的接口都需要 ADMIN 角色才能访问,以 /api/user/ 开头的接口需要 USER 或 ADMIN 角色。

5. JWT Token 生成和校验

在本小节中,将简单介绍 JWT Token 的生成和校验代码。

5.1 Token 生成

在用户登录成功之后,可以通过如下代码生成 Token:

String token = JWT.create()
        .withSubject(user.getUsername())
        .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
        .withClaim("role", user.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()))
        .sign(Algorithm.HMAC256(SECRET.getBytes()));

EXPIRATION_TIME 是 Token 过期时间,SECRET 是使用的密钥。

5.2 Token 校验

在用户请求接口的时候,需要根据请求头中的 Token 校验用户身份是否有效。校验代码如下:

String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
    try {
        Jws<Claims> claims = Jwts.parser()
                .setSigningKey(SECRET.getBytes())
                .parseClaimsJws(token.replace("Bearer ", ""));

        String username = claims.getBody().getSubject();

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities()
            );
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }

    } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
        logger.error("JWT token is invalid: {}", e.getMessage(), e);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.getWriter().write(e.getMessage());
        return;
    }
}

6. 示例代码

本篇文章中使用了两种方式实现 JWT 认证,下面提供方式二的完整代码:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String SECRET = "JWT_SECRET";
    private static final long EXPIRATION_TIME = 86400000L; // 1 day

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and().csrf().disable().authorizeRequests()
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER")
                .anyRequest().authenticated()
                .and().addFilterAfter(new JwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SECRET);
        return converter;
    }

    @Bean
    public JwtTokenEnhancer jwtTokenEnhancer() {
        return new JwtTokenEnhancer();
    }

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

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    private class JwtTokenEnhancer implements TokenEnhancer {
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
            User user = (User) authentication.getPrincipal();

            Map<String, Object> info = new HashMap<>();
            info.put("username", user.getUsername());

            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);

            return accessToken;
        }
    }

    private class JwtAuthorizationFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
            String token = request.getHeader("Authorization");
            if (token != null && token.startsWith("Bearer ")) {
                try {
                    Jwt jwt = JwtHelper.decode(token.replace("Bearer ", ""));
                    String jwtClaimsStr = jwt.getClaims();
                    JSONObject jsonObject = JSON.parseObject(jwtClaimsStr);
                    String username = jsonObject.getString("username");
                    User user = (User) userDetailsService.loadUserByUsername(username);
                    if (user != null) {
                        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                                user, null, user.getAuthorities()
                        );
                        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    }
                } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
                    logger.error("JWT token is invalid: {}", e.getMessage(), e);
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.getWriter().write(e.getMessage());
                    return;
                }
            }
            chain.doFilter(request, response);
        }
    }
}

完整的示例代码请移步:https://github.com/jackson0714/springboot-security-jwt-demo

至此,本文介绍了在 SpringBoot 中集成 Spring Security 和 JWT 实现接口权限认证的完整攻略,希望对读者有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot集成Spring security JWT实现接口权限认证 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Vue如何解决每次发版都要强刷清除浏览器缓存问题

    Vue可以通过以下三种方式解决每次发版都要强刷清除浏览器缓存的问题: 1. 禁用浏览器缓存 在Vue的配置文件中,设置productionSourceMap和filenameHashing为false,禁用浏览器的缓存。该设置会让每次打包生成的文件名都带有哈希值,以此保持每次生成的文件的唯一性。这样做可以确保所有用户在一次迭代后可以看到最新的内容。 示例: …

    Java 2023年6月15日
    00
  • IDEA 如何导入别人的javaweb项目进行部署

    下面是在 IDEA 中导入别人的 JavaWeb 项目并进行部署的详细攻略: 步骤1:下载并安装 IDEA 如果您还没有安装 IDEA,可以到 IntelliJ IDEA 官网下载对应版本并安装。安装过程中请按照提示一步一步操作即可。 步骤2:下载并解压缩 JavaWeb 项目 假设您已经获得了别人的 JavaWeb 项目源代码,接下来需要将其解压缩到本地。…

    Java 2023年6月2日
    00
  • Spring Boot Cache使用方法整合代码实例

    下面我将详细讲解“Spring Boot Cache使用方法整合代码实例”的完整攻略。 一、什么是Spring Boot Cache Spring Boot Cache是Spring Boot中的缓存框架,它提供了一种简单的方式来缓存数据的读取结果,从而减少不必要的计算并提升应用程序的性能。 二、Spring Boot Cache使用方法 1. 引入依赖 在…

    Java 2023年5月31日
    00
  • java基于jcifs.smb实现远程发送文件到服务器

    下面是关于“Java基于jcifs.smb实现远程发送文件到服务器”的完整攻略。 概述 jcifs.smb是一个java实现的SMB网络协议库,可以在java应用程序中实现与SMB服务器的连接。通过这个库,我们可以在java中实现与文件共享服务器之间的文件传输。在接下来的攻略中,我将详细介绍如何使用jcifs.smb库实现远程发送文件到服务器。 步骤一:引入…

    Java 2023年5月20日
    00
  • SpringMVC学习之JSTL条件行为和遍历行为详解

    SpringMVC学习之JSTL条件行为和遍历行为详解 什么是JSTL JSTL(JSP Standard Tag Library)是一个JSP标准标签库,包含JSP页面中常用的标签。JSTL有以下几种标签: Core(核心)标签:提供流程控制、迭代、变量赋值等功能。 Formatting(格式化)标签:提供日期、数值格式化等功能。 SQL 标签(depre…

    Java 2023年6月15日
    00
  • 详解java调用python的几种用法(看这篇就够了)

    下面是详解java调用python的几种用法的完整攻略。 1. 使用ProcessBuilder调用python ProcessBuilder可以通过指定命令行的方式启动子进程。因此使用ProcessBuilder可以很方便地调用python脚本,下面是示例代码: import java.io.*; public class CallPythonProces…

    Java 2023年5月23日
    00
  • 从原理聊JVM(三):详解现代垃圾回收器Shenandoah和ZGC

    作者:京东科技 康志兴 Shenandoah Shenandoah一词来自于印第安语,十九世纪四十年代有一首著名的航海歌曲在水手中广为流传,讲述一位年轻富商爱上印第安酋长Shenandoah的女儿的故事。 后来美国有一条位于Virginia州西部的小河以此命名,所以Shenandoah的中文译名为“情人渡”。 Shenandoah首次出现在Open JDK1…

    Java 2023年4月27日
    00
  • JS出现失效的情况总结

    JS出现失效的情况总结 JS是现代网站开发中必不可少的一部分,但在实际开发中,会遇到JS出现失效的情况,本文将对JS失效的各种可能情况进行总结,并给出具体解决方案。 1. JS文件未加载成功 当网页中引用的JS文件没有加载成功时,JS失效是最常见的情况之一。 解决方案 在HTML文件中检查script标签的引用路径是否正确,路径是否存在。 示例: <!…

    Java 2023年6月15日
    00
合作推广
合作推广
分享本页
返回顶部