Spring Boot OAuth2:从 cookie 中提取 JWT 进行身份验证

Posted

技术标签:

【中文标题】Spring Boot OAuth2:从 cookie 中提取 JWT 进行身份验证【英文标题】:Spring Boot OAuth2: extract JWT from cookie for authentication 【发布时间】:2018-12-24 15:16:36 【问题描述】:

我正在使用 Spring Boot 为我的应用程序构建一个简单的身份验证过程。 我有 AuthorizationServerConfig 和 ResourceServerConfig 设置,我的前端是 SPA。当我点击 /oauth/token 路由时,我得到了一个以前存储在 localStorage 中的 JWT,当我尝试点击资源服务器路由时,我使用这个 JWT 设置了授权标头,一切正常。

但是现在我想使用存储在 cookie 中的 JWT 进行授权,我如何配置它以便它与我当前的授权/资源服务器配置一起使用?我用谷歌搜索了一段时间,我能找到的最好的方法是设置一个自定义令牌提取器,但我不知道如何正确,提前谢谢你。

------------- 更新 --------------

我打开了@EnableAuthorizationServer 和@EnableResourceServer,EnableResourceServer 自动设置了一个OAuthAuthenticationProcessingFilter,这个过滤器用户承载头身份验证使用承载令牌提取器从请求头中提取,我查看了源代码,它被硬编码到库,如何自定义此过滤器以从 cookie 中提取 JWT?

【问题讨论】:

【参考方案1】:

从请求对象中读取 cookie 值并手动解析 jwt。 这是示例代码

public Jws<Claims> parseJWT(HttpServletRequest request) 
    Cookie cookie =  WebUtils.getCookie(request, "Token cookie name");
    if(cookie == null) 
        throw new SecurityException("Token not found from cookies");
    
    String token  = cookie.getValue();
    return Jwts.parser().setSigningKey("your signing Key").parseClaimsJws(token);

您可以创建请求过滤器并检查 jwt。

【讨论】:

【参考方案2】:

JWT 有很多实现。我正在使用这个。 io.jsonwebtoken

我正在添加一个令牌助手类,它具有验证、生成、刷新令牌的方法。您可以专注于 JWT 提取部分。

Jar 依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

JWT 助手类。它包含验证、刷新、生成令牌的方法。

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

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

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import com.test.dfx.common.TimeProvider;
import com.test.dfx.model.LicenseDetail;
import com.test.dfx.model.User;


@Component
public class TokenHelper 

    protected final Log LOGGER = LogFactory.getLog(getClass());

    @Value("$app.name")
    private String APP_NAME;

    @Value("$jwt.secret")
    public String SECRET;    //  Secret key used to generate Key. Am getting it from propertyfile

    @Value("$jwt.expires_in")
    private int EXPIRES_IN;  //  can specify time for token to expire. 

    @Value("$jwt.header")
    private String AUTH_HEADER;


    @Autowired
    TimeProvider timeProvider;

    private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512;  // JWT Algorithm for encryption


    public Date getIssuedAtDateFromToken(String token) 
        Date issueAt;
        try 
            final Claims claims = this.getAllClaimsFromToken(token);
            issueAt = claims.getIssuedAt();
         catch (Exception e) 
            LOGGER.error("Could not get IssuedDate from passed token");
            issueAt = null;
        
        return issueAt;
    

    public String getAudienceFromToken(String token) 
        String audience;
        try 
            final Claims claims = this.getAllClaimsFromToken(token);
            audience = claims.getAudience();
         catch (Exception e) 
            LOGGER.error("Could not get Audience from passed token");
            audience = null;
        
        return audience;
    

    public String refreshToken(String token) 
        String refreshedToken;
        Date a = timeProvider.now();
        try 
            final Claims claims = this.getAllClaimsFromToken(token);
            claims.setIssuedAt(a);
            refreshedToken = Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith( SIGNATURE_ALGORITHM, SECRET )
                .compact();
         catch (Exception e) 
            LOGGER.error("Could not generate Refresh Token from passed token");
            refreshedToken = null;
        
        return refreshedToken;
    

    public String generateToken(String username) 
        String audience = generateAudience();
        return Jwts.builder()
                .setIssuer( APP_NAME )
                .setSubject(username)
                .setAudience(audience)
                .setIssuedAt(timeProvider.now())
                .setExpiration(generateExpirationDate())
                .signWith( SIGNATURE_ALGORITHM, SECRET )
                .compact();
    



    private Claims getAllClaimsFromToken(String token) 
        Claims claims;
        try 
            claims = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token)
                    .getBody();
         catch (Exception e) 
            LOGGER.error("Could not get all claims Token from passed token");
            claims = null;
        
        return claims;
    

    private Date generateExpirationDate() 
        long expiresIn = EXPIRES_IN;
        return new Date(timeProvider.now().getTime() + expiresIn * 1000);
    

    public int getExpiredIn() 
        return EXPIRES_IN;
    

    public Boolean validateToken(String token, UserDetails userDetails) 
        User user = (User) userDetails;
        final String username = getUsernameFromToken(token);
        final Date created = getIssuedAtDateFromToken(token);
        return (
                username != null &&
                username.equals(userDetails.getUsername()) &&
                        !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
        );
    

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) 
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    

    public String getToken( HttpServletRequest request ) 
        /**
         *  Getting the token from Authentication header
         *  e.g Bearer your_token
         */
        String authHeader = getAuthHeaderFromHeader( request );
        if ( authHeader != null && authHeader.startsWith("Bearer ")) 
            return authHeader.substring(7);
        

        return null;
    

    public String getAuthHeaderFromHeader( HttpServletRequest request ) 
        return request.getHeader(AUTH_HEADER);
    



最后是你的控制器类

public void validateToken(HttpServletRequest request) 
    Cookie cookie =  WebUtils.getCookie(request, "TOKEN_NAME");
    if(cookie == null) 
        throw new SecurityException("JWT token missing");
    
    String token  = cookie.getValue();  // JWT Token

    Claims claims =   TokenHelper.getAllClaimsFromToken(token); // claims will be null if Token is invalid


【讨论】:

以上是关于Spring Boot OAuth2:从 cookie 中提取 JWT 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot OAuth2:从 cookie 中提取 JWT 进行身份验证

Spring Boot OAuth2 - GoogleApi - 无法从令牌获取用户详细信息

Spring boot security oauth2 从 cookie 获取 access_token

创建隐式JWT时的Spring Boot OAuth2?

Spring Boot 和 OAuth2 CORS

Spring Boot 和 OAuth2 CORS