单点登录(SSO)+ JWT 验证最佳实践?

huangapple 未分类评论43阅读模式
英文:

SSO + JWT Validation Best Practice?

问题

以下是翻译好的内容:

所以我想询问有关JWT验证的问题。

我的Spring Boot项目为除了登录和注销端点之外的任何API请求实现了JWT验证。
我的登录和注销端点(..../api/v1/auth/login,..../api/v1/auth/logout)是基于另一个源的单点登录(SSO)。
因此,当我使用该SSO登录时,它们会将他们的JWT令牌返回给我的后端处理。

然后,当我访问我的另一个API时,我希要在使用之前先验证生成的JWT,但是尽管我的JWT匹配,它总是返回401错误。

接下来是我的认证类:

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;

public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    @Value("${xxxx.app.client_secret}")
    private String clientSecret;
    
    private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
    
    AuthenticationFilter(final RequestMatcher requiresAuth) {
        super(requiresAuth);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {

        String tokenRequest = httpServletRequest.getHeader("Authorization");
        validateJwtToken(tokenRequest.substring(7));
        Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(tokenRequest, tokenRequest);
        return getAuthenticationManager().authenticate(requestAuthentication);
    }

    
    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }
}

validateJwtToken 方法中,没有问题,因为它给我返回了 true,但当它返回到 attemptAuthentication 方法时,在该类中捕获了以下错误:

catch (InternalAuthenticationServiceException failed) {
    logger.error(
        "An internal error occurred while trying to authenticate the user.",
        failed);
    unsuccessfulAuthentication(request, response, failed);

    return;
}

抱歉我的代码不够好,这是我第一次将JWT安全性应用到项目中。那么在这种情况下,我应该怎么做呢?谢谢,非常感谢任何帮助或建议。

英文:

so i want to ask question about JWT Validation.

My springboot project implementing jwt validation for any api request except login and logout endpoint.
my login and logout endpoint (..../api/v1/auth/login, ..../api/v1/auth/logout) is SSO based from another source.
So when i login with that SSO, they are returning their JWT token to my backend process.

then, when i hit my another api, i want to validate it first with the generated jwt before, but although my jwt was match, it always give 401 error.

then this is my authentication class :

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;


import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;


public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    @Value("${xxxx.app.client_secret}")
    private String clientSecret;
    
    private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
	
    AuthenticationFilter(final RequestMatcher requiresAuth) {
        super(requiresAuth);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {

        String tokenRequest = httpServletRequest.getHeader("Authorization");
        validateJwtToken(tokenRequest.substring(7));
        Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(tokenRequest, tokenRequest);
        return getAuthenticationManager().authenticate(requestAuthentication);


    }

    
    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }
}

in validateJwtToken method, there is no problem since it give me true return, but when it back to attemptAuthentication, it catch error like this in that class

catch (InternalAuthenticationServiceException failed) {
			logger.error(
					"An internal error occurred while trying to authenticate the user.",
					failed);
			unsuccessfulAuthentication(request, response, failed);

			return;

sorry for my bad code, this is my first time implementing a jwt security to my project. So what should i do when i have case like this ? thank you, any help or suggestion will be appreciated.

答案1

得分: 0

你可以尝试这样做如果令牌是由用户名生成为主题那么从令牌中解析出它并传递给UsernamePasswordAuthenticationToken在数据库中进行验证并设置在SecurityContext中

    String username = Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken).getBody().getSubject();
    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
    SecurityContextHolder.getContext().setAuthentication(authentication);

Spring Security UserDetailsService 自定义实现示例

    public class UserDetailsServiceImpl implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // ApplicationUser - 表示应用程序中用户的实体示例
            ApplicationUser user = // 在这里调用返回用户名的 SSO 服务
            if (user == null) {
                throw new UsernameNotFoundException(username);
            }
            return new User(user.getUsername(), user.getPassword(), emptyList());
        }
    }

在应用程序的 SecurityConfiguration 类中的配置

    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private UserDetailsServiceImpl userDetailsServiceImpl;
        
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsServiceImpl);
        }
        // 其他配置 ...
    }
英文:

Can you try this: If the token is generated by username as Subject, then parse it from token and pass to UsernamePasswordAuthenticationToken to verify in database and set in SecurityContext

String username = Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken).getBody().getSubject();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList&lt;&gt;()); 
SecurityContextHolder.getContext().setAuthentication(authentication);

Spring Security UserDetailsService custom implementation ex:

public class UserDeatilsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   // ApplicationUser - example for entity representing user in application
	ApplicationUser user = // here call your SSO service returing username
	if (user == null) {
		throw new UsernameNotFoundException(username);
	}
	return new User(user.getUsername(), user.getPassword(), emptyList());
}}

Configuration in Application SecurityConfiguration class:

public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDeatilsServiceImpl userDetailsServiceImpl;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
	auth.userDetailsService(userDetailsServiceImpl);
}
// other configurations ...

huangapple
  • 本文由 发表于 2020年7月24日 19:20:07
  • 转载请务必保留本文链接:https://java.coder-hub.com/63072522.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定