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日

相关文章

  • Sprint Boot @NegativeOrZero使用方法详解

    @NegativeOrZero是Spring Boot中的一个注解,用于标记一个字段或方法参数必须为非正数。在本文中,我们将详细介绍@NegativeOrZero注解的作用和使用方法,并提供两个示例。 @NegativeOrZero注解的作用 @NegativeOrZero注解用于标记一个字段或方法参数必须为非正数。当使用@NegativeOrZero注解标…

    Java 2023年5月5日
    00
  • Spring Security十分钟入门教程

    Spring Security 十分钟入门教程 Spring Security 是一个基于 Spring 框架的安全框架,能够为 web 应用程序提供身份验证和授权的支持。 开始之前 在开始学习之前,需要具备以下知识: Spring 框架基础 Maven 项目管理工具 Spring Boot 基础知识 步骤 步骤一:创建一个 Spring Boot Web …

    Java 2023年6月3日
    00
  • springboot+spring data jpa实现新增及批量新增方式

    关于“springboot+spring data jpa实现新增及批量新增方式”的完整攻略,具体步骤如下: 步骤一:添加依赖 在pom.xml文件中添加Spring Data JPA的依赖: <dependency> <groupId>org.springframework.data</groupId> <arti…

    Java 2023年6月2日
    00
  • 常见的Java锁有哪些?

    常见的Java锁有以下几种: 1. synchronized关键字 synchronized是Java提供的最基本的锁,可以用于方法或代码块中。它采用悲观锁的机制,在同一时间只能有一个线程获得该锁,其他线程需要等待。 示例: public class SynchronizedExample { private int count = 0; public sy…

    Java 2023年5月11日
    00
  • hibernate-validator如何使用校验框架

    下面是详细讲解“hibernate-validator如何使用校验框架”的完整攻略。 简介 hibernate-validator是一款基于Java Bean Validation标准的校验框架,能够轻松地将校验逻辑应用到JavaBean中,大大提高开发效率,减少出错几率。 使用步骤 1. 引入依赖 在项目的pom.xml文件中添加如下依赖配置: <d…

    Java 2023年5月20日
    00
  • Spring与Web整合实例

    针对“Spring与Web整合实例”的完整攻略,我将从以下几个方面进行详细讲解: 环境搭建 Spring与Web整合配置 示例展示 一、环境搭建 在进行Spring与Web整合的实例之前,需要先搭建好相应的环境,主要包括以下几个方面: JDK环境的安装与配置 Tomcat服务器的安装与配置 Maven工具的安装与配置 Spring框架与Spring MVC组…

    Java 2023年5月19日
    00
  • Java实现顺序栈的示例代码

    下面是Java实现顺序栈的示例代码的完整攻略。 什么是顺序栈 顺序栈是一种使用数组实现的栈,也称作数组栈。其基本特点是后进先出,即最后进栈的元素最先出栈。 顺序栈的实现思路 顺序栈需要使用数组保存元素,因此先声明一个数组; 定义一个变量top表示栈顶元素的下标,初始值为-1; 入栈操作时,将元素插入到数组中,top的值加1; 出栈操作时,将栈顶元素弹出,to…

    Java 2023年5月19日
    00
  • Spring MVC+FastJson+Swagger集成的完整实例教程

    Spring MVC+FastJson+Swagger集成的完整实例教程 Spring MVC是一个非常流行的Java Web框架,它提供了很多方便的功能。FastJson是一个高性能的JSON库,它可以将Java对象转换为JSON格式的字符串。Swagger是一个API文档生成工具,它可以自动生成API文档,并提供交互式API测试界面。本文将详细讲解如何使…

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