欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用SpringBoot和SpringSecurity实现 JWT 认证与授权

程序员文章站 2023-08-26 15:34:55
在学习时 JWT 写的Demo,在此记录方便复习Demo源码。使用 JWT ,简单来说就是将客户端传来的认证信息加密成 token,然后返回给客户端,客户端每个请求都携带上这个 token,服务器通过解析这个 token,完成对用户的认证授权。iJWT官网原理SpringSecurity 是使用一系列 Filter 来实现认证授权,所以我们只要编写自己的 Filter ,放在 FilterChain 的恰当位置。即可实现自己的认证授权规则。代码POM依赖 &...

在学习时 JWT 写的Demo,在此记录方便复习。Demo源码。使用 JWT ,简单来说就是将客户端传来的认证信息加密成 token,然后返回给客户端,客户端每个请求都携带上这个 token,服务器通过解析这个 token,完成对用户的认证授权。JWT官网

原理

SpringSecurity 是使用一系列 Filter 来实现认证授权,所以我们只要编写自己的 Filter ,放在 FilterChain 的恰当位置。即可实现自己的认证授权规则。

代码

  1. POM依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.3</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  1. 项目结构
    使用SpringBoot和SpringSecurity实现 JWT 认证与授权

  2. JWT 工具类

package com.example.jwtdemo.util;

import io.jsonwebtoken.*;

import java.util.Date;
import java.util.Map;

/**
 * @author: leoi
 * @create: 2021/3/3 11:45
 */
public class JwtUtils {

    // 签发者
    private static final String ISS = "leoiwan";

    // 秘钥
    public static final String SECRET = "secret";

    // token前缀
    public static final String TOKEN_PREFIX = "Bearer ";

    // 请求头名称
    public static final String HEADER_STRING = "Authentication";

    // token有效时长
    private static final long EXPIRATION = 60 * 60 * 3 * 1000L;

    // 将 Map 转换为 JWT
    public static String createToken(Map<String, Object> claims) {
        return Jwts
                .builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setClaims(claims)
                .setIssuer(ISS)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .compact();
    }

    // 将 JWT 转换为 Map
    public static Claims parseToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
            e.printStackTrace();
        }
        return claims;
    }


    private JwtUtils() {
    }


}

  1. UserDetails 与 UserDetailsService

    实现这个两个接口,可以让我们自定义用户认证。UserDetails 定义了如何获得用户名,密码,权限等;UserDetailsService 只有一个方法,那就是根据用户名得到 UserDetails 。SpringSecurity 源码如下:

    UserDetails

    public interface UserDetails extends Serializable {
    
    	Collection<? extends GrantedAuthority> getAuthorities();
    
    	String getPassword();
    
    	String getUsername();
    
    	boolean isAccountNonExpired();
    
    	boolean isAccountNonLocked();
    
    	boolean isCredentialsNonExpired();
    
    	boolean isEnabled();
    
    }
    

    UserDetailsService

    public interface UserDetailsService {
    
    	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    	
    }
    

    具体实现:

    UserInfo

    package com.example.jwtdemo.entity;
    
    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    import java.util.Collection;
    import java.util.Collections;
    
    /**
     * @author: leoi
     * @create: 2021/3/3 11:58
     */
    @Data
    public class UserInfo implements UserDetails {
    
        private static final long serialVersionUID = 347318048229419086L;
    
        private final String username;
    
        public UserInfo(String username) {
            this.username = username;
        }
    
        @Override
        public String toString() {
            return "UserInfo{" +
                    "username='" + username + "',\n" +
                    "password='" + getPassword() + "',\n" +
                    "authorities'=" + getAuthorities() + "',\n" +
                    '}';
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
        }
    
        @Override
        public String getPassword() {
            return new BCryptPasswordEncoder().encode("123456");
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
    

    UserServiceImpl

    package com.example.jwtdemo.service;
    
    import com.example.jwtdemo.entity.UserInfo;
    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;
    
    /**
     * @author: leoi
     * @create: 2021/3/3 12:01
     */
    @Service
    public class UserServiceImpl implements UserDetailsService {
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            return new UserInfo(username);
        }
    }
    
    
  2. 自定义 Filter

    package com.example.jwtdemo.config;
    
    import com.example.jwtdemo.util.JwtUtils;
    import io.jsonwebtoken.Claims;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    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.Date;
    
    /**
     * @author: leoi
     * @create: 2021/3/3 17:25
     */
    @Slf4j
    public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
        @Qualifier("userServiceImpl")
        @Autowired
        private UserDetailsService userService;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String token = request.getHeader(JwtUtils.HEADER_STRING);
            // 需要携带正确的前缀
            if (token != null && token.startsWith(JwtUtils.TOKEN_PREFIX)) {
                // 解析 Jwt 并得到用户名
                token = token.substring(JwtUtils.TOKEN_PREFIX.length());
                log.info("checking token:{}", token);
                Claims jwtParams = JwtUtils.parseToken(token);
                String username = (String) jwtParams.get("subject");
                log.info("checking username:{}", username);
                // 是否为有效 Token
                if (username != null && jwtParams.getExpiration().after(new Date())) {
                    // 构建 authentication 对象
                    UserDetails userInfo = userService.loadUserByUsername(username);
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userInfo.getUsername(), null, userInfo.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    log.info("authenticated User:{}", username);
                    log.info("authenticated Authorities:{}", userInfo.getAuthorities());
                    // 放入 Security 上下文
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
            filterChain.doFilter(request, response);
        }
    }
    
    
  3. Security配置

    package com.example.jwtdemo.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    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.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @author: leoi
     * @create: 2021/3/3 12:08
     */
    @Slf4j
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        @Qualifier("userServiceImpl")
        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.csrf()
                    .disable();
    
            http.authorizeRequests()
                    .antMatchers("/", "/login/**").permitAll()
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .anyRequest().authenticated();
            
            http.exceptionHandling()
                    .authenticationEntryPoint((req, resp, e) -> {
                        log.info("匿名用户访问无权限资源");
                        resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
                    })
                    .accessDeniedHandler((req, resp, e) -> {
                        log.info("认证用户访问无权限资源");
                        resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
                    });
    
            // 将自定义过滤器添加在 UsernamePasswordAuthenticationFilter 之后
            http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public JwtAuthenticationFilter jwtAuthenticationFilter() {
            return new JwtAuthenticationFilter();
        }
    }
    
    

测试

  1. 登录测试

    package com.example.jwtdemo.controller;
    
    import com.example.jwtdemo.util.JwtUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.HashMap;
    
    /**
     * @author: leoi
     * @create: 2021/3/3 12:14
     */
    @RestController
    public class AuthenticationController {
    
        @Autowired
        @Qualifier("userServiceImpl")
        private UserDetailsService userService;
    
        @PostMapping("/login")
        public String login(@RequestParam String username, @RequestParam String password) {
            UserDetails userDetails = userService.loadUserByUsername(username);
    
            HashMap<String, Object> claims = new HashMap<String, Object>() {
                private static final long serialVersionUID = 3803002510172418126L;
    
                {
                    put("subject", userDetails.getUsername());
                    put("password", userDetails.getPassword());
                }
            };
            return JwtUtils.createToken(claims);
        }
    }
    
    
    1. 获取 Token
      使用SpringBoot和SpringSecurity实现 JWT 认证与授权

    2. 获取认证对象
      使用SpringBoot和SpringSecurity实现 JWT 认证与授权

  2. 授权测试

    package com.example.jwtdemo.controller;
    
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author: leoi
     * @create: 2021/3/3 12:42
     */
    @RestController
    @RequestMapping("/role")
    public class AuthorizationController {
    
        @GetMapping("/user")
        @PreAuthorize("hasAnyRole('ROLE_USER')")
        public String user() {
            return "access success,has role [user]";
        }
    
        @GetMapping("/admin")
        @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
        public String admin() {
            return "access success,has role [admin]";
        }
    }
    
    
    1. 有权限的访问
      使用SpringBoot和SpringSecurity实现 JWT 认证与授权

    2. 无权限的访问
      使用SpringBoot和SpringSecurity实现 JWT 认证与授权

本文地址:https://blog.csdn.net/qq_43619402/article/details/114334538

相关标签: spring spring boot