Spring Boot实战之Filter实现使用JWT进行接口认证

jwt(json web token)

用户发送按照约定,向服务端发送 Header、Payload 和 Signature,并包含认证信息(密码),验证通过后服务端返回一个token,之后用户使用该token作为登录凭证,适合于移动端和api

jwt使用流程

JWT_workflow

代码实现

1. 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.johnfnash.learn.springboot</groupId>
    <artifactId>jwt-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jwt-demo</name>
    <description>Demo project for JWT</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. JWT token生成及解析工具类

package com.johnfnash.learn.jwt.util;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.util.encoders.Base64;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtUtils {

    /**
     * 签发 JWT
     * 
     * @param id
     * @param subject   可以是 JSON 数据,尽可能少
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String userId, String subject, long ttlMillis) {
        SignatureAlgorithm algorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // header Map
        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        SecretKey secretKey = generalKey();

        Claims claims = Jwts.claims();
        claims.setId(UUID.randomUUID().toString()) // jti (JWT ID),防止jwt被重新发送
                .setSubject(subject) // 主题
                .setIssuedAt(now) // 签发时间
                .setIssuer("user"); // 签发者

        // payload 中 放入自定义信息
        Map<String, Object> selfMap = new HashMap<>();
        selfMap.put("userId", userId);
        claims.putAll(selfMap);

        JwtBuilder builder = Jwts.builder().setHeader(map) // header
                .setClaims(claims) // 使用 JSON 实例设置 payload
                .signWith(algorithm, secretKey); // 签名算法以及密钥
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate); // 过期时间
        }
        return builder.compact();
    }

    private static SecretKey generalKey() {
        byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECRET);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 
     * 解析JWT字符串
     * 
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) {
        Claims claims = null;
        try {
            SecretKey secretKey = generalKey();
            claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
        } catch (Exception e) {
            System.err.println("token 校验失败");
        }
        return claims;
    }


    public static void main(String[] args) {
        String token = createJWT("aaaa", "同步", 60000L);
        System.out.println("token: " + token);
        System.out.println(parseJWT(token));
    }

}

3. JWT 校验 filter

package com.johnfnash.learn.jwt.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

import org.apache.catalina.servlet4preview.http.HttpServletRequest;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import com.johnfnash.learn.jwt.exception.ServiceException;
import com.johnfnash.learn.jwt.util.JwtUtils;

import io.jsonwebtoken.Claims;

/**
 * JWT 校验 filter
 */
@WebFilter(filterName = "jwtFilter", urlPatterns = "/hello/*")
public class HTTPBearerAuthorizeAttribute implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if("options".equals(httpRequest.getMethod())) {
            chain.doFilter(request, response);
            return;
        }
        
        String auth = httpRequest.getHeader("Authorization");
        if(auth != null && auth.length() > 7) {
            String headStr = auth.substring(0, 6).toLowerCase();
            if (headStr.compareTo("bearer") == 0)
            {
                auth = auth.substring(7, auth.length());
                Claims claims = JwtUtils.parseJWT(auth);
                if(claims != null) {
                    chain.doFilter(request, response);
                    return;
                }
            }
        }
        
        // token 校验失败,抛出异常
        throw new ServiceException("token校验失败");
    }

    @Override
    public void destroy() {
    }

}

注:

  • 从 请求 的header 从获取传入的 token 并进行校验,校验通过则请求走下去;否则,抛出异常,在异常统一处理类中处理。
  • 需要在启动类中添加 @ServletComponentScan 注解扫描定义的 filter
@SpringBootApplication
@ServletComponentScan
public class JwtDemoApplication {

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

}

其中,异常处理类代码如下:

package com.johnfnash.learn.jwt.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.johnfnash.learn.jwt.dto.JsonResult;
import com.johnfnash.learn.jwt.enums.StatusCodeEnum;
import com.johnfnash.learn.jwt.exception.ServiceException;

/**
 * 统一异常处理
 *
 */
@ControllerAdvice
@ResponseBody
public class UnifiedExceptionHandler    {

    @ExceptionHandler(value = ServiceException.class)
    public JsonResult<?> handleAuthorizationException(HttpServletRequest request,
            HttpServletResponse response, ServiceException ex) {
        
        JsonResult<?> jr = JsonResult.of(StatusCodeEnum.AUTH_ERR.getCode(), null);
        if (ex instanceof ServiceException) {
            jr.setMsg(ex.getMessage() != null ? ex.getMessage() : "授权校验失败");
        } else {
            jr.setMsg("授权校验失败");
        }

        return jr;
    }

}

注:其中 ServiceException 为自定义的业务异常类,JsonResult 为对响应结果进行的一个封装。

4. token 的获取

登录成功之后获取JWT,大致流程如下:

package com.johnfnash.learn.jwt.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.johnfnash.learn.jwt.util.JwtUtils;

@RestController
@RequestMapping("/auth")
public class AuthController {

     @RequestMapping(value="/login",method = RequestMethod.POST)
     public String login(String loginName, String password) {
         // 1. 进行账号、密码校验
         
         // 2. 校验通过之后
         String userId = "adadsad";
         String jwt = JwtUtils.createJWT(userId, loginName, 1800000);
         return jwt;
     }
    
}

5. API 的 token 校验

package com.johnfnash.learn.jwt.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.johnfnash.learn.jwt.dto.JsonResult;

@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("detail")
    public JsonResult<String> hello() {
        return JsonResult.of("hello world!");
    }
    
}

filter 中定义了需要进行过滤的请求的 url pattern,这个 controller 下的接口就是要被过滤,进行token校验的。

6. 测试

1) 登录,获取token

token_request

2) 使用上面获取的token进行接口调用

未使用token,获取token错误,或者token过期时

token_auth_fail

使用正确的token时

token_auth_success

注意:token 放在 请求的 Authorization 头中。

本文参考:Spring Boot实战之Filter实现使用JWT进行接口认证

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用Spring BootJwt进行身份验证可以保护您的应用程序免受未经授权的访问。下面是使用Spring BootJwt进行身份验证的步骤: 1. 添加相关依赖 在 `pom.xml` 文件中添加以下依赖: ```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. 创建JwtUtils类 创建一个 `JwtUtils` 类,用于生成和解析Jwt令牌。以下是一个示例实现: ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userDetails.getUsername()); } private String createToken(Map<String, Object> claims, String subject) { Date now = new Date(); Date expirationDate = new Date(now.getTime() + expiration); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(now) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } public boolean isTokenExpired(String token) { final Date expirationDate = extractExpiration(token); return expirationDate.before(new Date()); } public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } } ``` 3. 创建JwtRequestFilter 创建一个 `JwtRequestFilter` 类,用于拦截所有安全请求并验证Jwt令牌。以下是一个示例实现: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; 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; @Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtils jwtUtils; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtils.extractUsername(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtils.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } filterChain.doFilter(request, response); } } ``` 4. 创建JwtAuthenticationController 创建一个 `JwtAuthenticationController` 类,用于处理用户身份验证请求并返回Jwt令牌。以下是一个示例实现: ```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.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @RestController public class JwtAuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtils jwtUtils; @Autowired private PasswordEncoder passwordEncoder; @PostMapping("/authenticate") public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest jwtRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(jwtRequest.getUsername(), jwtRequest.getPassword())); } catch (Exception e) { throw new Exception("Incorrect username or password", e); } final UserDetails userDetails = userDetailsService.loadUserByUsername(jwtRequest.getUsername()); final String token = jwtUtils.generateToken(userDetails); return ResponseEntity.ok(new JwtResponse(token)); } @PostMapping("/register") public ResponseEntity<?> saveUser(@RequestBody UserDto userDto) { User user = new User(userDto.getUsername(), passwordEncoder.encode(userDto.getPassword())); userDetailsService.save(user); return ResponseEntity.ok(user); } } ``` 5. 配置WebSecurityConfigurerAdapter 最后,我们需要配置WebSecurityConfigurerAdapter以启用基于Jwt的身份验证。以下是一个示例实现: ```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.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.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @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("/authenticate", "/register").permitAll() .anyRequest().authenticated() .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 现在,您的Spring Boot应用程序已经配置使用Jwt进行身份验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值