spring-boot Jwt实现token的发放和验证

一、具体操作

具体参照这篇

二、几点补充

1、引入Jwt依赖

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.7.0</version>
		</dependency>

2、在filter中验证token时,过期和非法的token都会抛出异常,可以自定义bean继承自BasicErrorController来进行统一的异常处理(返回给前端固定的Json内容,实际使用时和Js交互还会遇到跨域问题,要给response加上相关的请求头)。

3、贴波自己写的代码

注:基于 boot 1.X, 2.X的BasicErrorController有所不同

不懂的留言

@Configuration
public class JwtConfig {

    @Bean
    public FilterRegistrationBean jwtFilter() {
        final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new JwtFilter());
        registrationBean.addUrlPatterns("/test/*");//配置对应路径的接口使用此 filter
        return registrationBean;
    }
}
@RestController
public class MyCommonErrorController extends BasicErrorController {
    //统一处理filter抛出的token相关的异常 返回给前端标准格式的json和装填码
    private static final String PATH = "/error";
    private static final String TOKEN_MISS = "Missing or invalid Authorization header";
    private static final String TOKEN_EXPIRED = "token expired";
    private static final String TOKEN_INVALID = "token invalid";
    private static final String TOKEN_ERROR = "error";

    public MyCommonErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    @Override
    @RequestMapping(
            produces = {"application/json"}
    )
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        //加入跨域相关内容
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Expose-Headers", "X-Total-Count");
        response.setHeader("Access-Control-Allow-Headers", "origin, x-requested-with, x-http-method-override, content-type, Authentication, Authorization, hospital");
        response.setHeader("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS, HEAD, PATCH");
        
        HttpStatus status = this.getStatus(request);
        Map<String, Object> errorAttributes = this.getErrorAttributes(request, true);
        String message = (String)errorAttributes.get("message");
        Map<String, Object> body = new LinkedHashMap<>(16);
        body.put("code", getCode(message));
        body.put("message", message);
        body.put("data", null);
        return new ResponseEntity(body, status);
    }

    private int getCode(String msg){
        if (TOKEN_MISS.equals(msg)){
            return -1;
        }else if (TOKEN_EXPIRED.equals(msg)){
            return -2;
        }else if (TOKEN_INVALID.equals(msg)){
            return -3;
        }else if (TOKEN_ERROR.equals(msg)){
            return -4;
        }else {
            return -5;
        }
    }

    @Override
    public String getErrorPath() {
        return PATH;
    }
}
@RestController
public class TokenController {
    //登陆接口
    @PostMapping("/login")
    public Object login(@RequestBody LoginRequest loginRequest){
        //假设验证过了用户名和密码 发token
        // Create Twt token
        return generateToken(loginRequest.getUsername());
    }

    private String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>(16);
        claims.put("sub", username);
        claims.put("created", new Date());
        return generateToken((claims));
    }
    
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims)        //payload
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 1000L))  //过期时间
                .signWith(SignatureAlgorithm.HS512, "nicai").compact();  //加密方式
    }
}
@RestController
@RequestMapping("/test")
public class TestController {
    //用于测试token的验证
    @GetMapping("")
    public Object getTest(HttpServletRequest request){
        System.out.println(request.getAttribute("claims"));
        Map<String, String> claims = (Map<String, String>) request.getAttribute("claims");
        return "test"; 
    }
}
@Data
public class TokenException extends Exception {
    //自定义异常类型
    private int code;
    private String msg;

    public TokenException() {
    }

    public TokenException(int code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }
}
public class JwtFilter extends GenericFilterBean{
    //Jwtconfig中配置的filter 用于Jwt token的验证工作 配置时可以指定对应的路径
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        String authHeader = request.getHeader("Authorization");

        //规避探测性质的 OPTIONS请求
        String optionsString = "OPTIONS";
        String bearerString = "Bearer ";
        if (optionsString.equals(request.getMethod())){
            response.setStatus(HttpServletResponse.SC_OK);
            filterChain.doFilter(servletRequest, servletResponse);
        }else {
            //验证token
            if (StringUtils.isEmpty(authHeader) || !authHeader.startsWith(bearerString)){
                    throw new ServletException(new TokenException(-1, "Missing or invalid Authorization header"));
            }else {
                String token = authHeader.substring(bearerString.length());
                try {
                    //使用jwt paser来验证签名
                    Claims claims = Jwts.parser().setSigningKey("nicai").parseClaimsJws(token).getBody();
                    request.setAttribute("claims", claims);
                }catch (ExpiredJwtException e){
                    throw new ServletException(new TokenException(-2, "token expired"));
                }catch (SignatureException e){
                    throw new ServletException(new TokenException(-3, "token invalid"));
                }catch (Exception e){
                    throw new ServletException(new TokenException(-4, "error"));
                }

            }
            filterChain.doFilter(servletRequest, servletResponse);
        }

    }

}






  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring BootSpring Security是非常常用的Java框架和库,用于构建安全和可扩展的Web应用程序。JWT(JSON Web Token)是一种用于在网络应用间安全传递身份验证和声明信息的标准。 要在Spring Boot中使用Spring Security和JWT,你需要进行以下步骤: 1. 添加依赖:在你的项目的pom.xml文件中添加Spring Security和JWT的依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建JWT工具类:编写一个JWT工具类,用于生成和解析JWT。 ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.core.userdetails.UserDetails; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtUtil { private static final String SECRET_KEY = "your-secret-key"; private static final long EXPIRATION_TIME = 864_000_000; // 10 days public static String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String extractUsername(String token) { return extractClaims(token).getSubject(); } public static Date extractExpiration(String token) { return extractClaims(token).getExpiration(); } private static Claims extractClaims(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } } ``` 3. 配置Spring Security:创建一个继承自WebSecurityConfigurerAdapter的配置类,用于配置Spring Security。 ```java 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.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.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @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.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Bean public PasswordEncoder passwordEncoder() { return new Pbkdf2PasswordEncoder(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 4. 创建UserDetails实现类:实现Spring Security的UserDetails接口,用于获取用户信息。 ```java import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; public class CustomUserDetails implements UserDetails { private String username; private String password; public CustomUserDetails(String username, String password) { this.username = username; this.password = password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(() -> "ROLE_USER"); } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } // Other UserDetails methods... @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } ``` 5. 创建认证和授权的Controller:创建一个RestController,用于处理用户认证和授权的请求。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody AuthRequest authRequest) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername()); String token = JwtUtil.generateToken(userDetails); return ResponseEntity.ok(token); } } class AuthRequest { private String username; private String password; // getters and setters... } ``` 这样,你就可以在Spring Boot应用程序中使用Spring Security和JWT实现认证和授权了。当用户登录时,会生成一个JWT,并在以后的请求中使用该JWT进行身份验证。 请注意,以上代码只是一个简单的示例,你可能需要根据你的实际需求进行适当的修改和扩展。另外,确保保护敏感信息(如密钥)的安全。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值