简体   繁体   English

通过Spring Security保护的Web应用中的REST用户身份验证

[英]User Authentication with REST in a web app secured with Spring Security

My Spring Boot-based web app authenticates against a remote REST api. 我的基于Spring Boot的Web应用通过远程REST api进行身份验证。 To do so, I have extended AbstractUserDetailsAuthenticationProvider like so: 为此,我扩展了AbstractUserDetailsAuthenticationProvider如下所示:

public class RestAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        String password = (String) authentication.getCredentials();
        Credentials creds = new Credentials();
        creds.setUsername(username);
        creds.setPassword(password);

        RestTemplate template = new RestTemplate();
        try {
            ResponseEntity<Authentication> auth = template.postForEntity("http://localhost:8080/api/authenticate",
                    creds, Authentication.class);
            if (auth.getStatusCode() == HttpStatus.OK) {

                String token = auth.getHeaders().get("Authorization").get(0); // Great! Now what?

                return new User(authentication.getName(), password,
                        Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
            }

            throw new BadCredentialsException("Failed to authenticate, server returned " + auth.getStatusCodeValue());

        } catch (HttpClientErrorException e) { // Check for type of error
            throw new BadCredentialsException(e.getStatusText());
        }
    }
}

This works great. 这很好。 However my problem is that subsequent access to the API will require the API key being provided in the RestTemplate headers. 但是我的问题是,对API的后续访问将需要在RestTemplate标头中提供API密钥。 This is the line : 这是一行:

String token = auth.getHeaders().get("Authorization").get(0); // Great! Now what?  

What I'm after is some way to persist that token at the session level for future access. 我需要的是某种在会话级别上持久保存令牌以供将来访问的方法。 Down the line, I'm trying to do something along the lines of: 一直以来,我正在尝试按照以下方式进行操作:

    SecurityContext context = SecurityContextHolder.getContext();
    Authentication userDetails = context.getAuthentication() ;

where somehow userDetails would contain the API key. 其中userDetails将以某种方式包含API密钥。

Any suggestions? 有什么建议么?

Thanks! 谢谢!

This is you designing & implementing your own security solution. 这是设计和实施自己的安全解决方案。 The framework provides you a way to implement how you resolve UserDetails based on credentials . 该框架为您提供了一种基于凭证来实现解析UserDetails方法。

I see the question for subsequent invocations of this. 我看到此问题的后续调用。 For that reason "old school" web apps have sessions, but "new school" thinks it's "not cool" anymore as it "does not scale" ( or whatever other reasons ) and I assume you're one of those. 因此,“老派” Web应用程序具有会话,但是“新派”认为它不再“酷”,因为它“不扩展”(或其他任何原因),我想您就是其中之一。

If you don't want to use sessions - then you need to get your user from the database ( or cache, or session store, or what you like ) based on some identified. 如果您不想使用会话-那么您需要根据确定的内容从数据库(或缓存,会话存储或您喜欢的对象)中获取用户。

Your current implementation provides this feature by using username and password , but I assume you don't want to keep that in memory & send with every request. 当前的实现使用用户名密码来提供此功能,但是我想您不想将其保留在内存中并随每个请求发送。

So you want to generate a token, store it in the database ( or cache, or token store, or session store ) and then implement another AuthenticationProvider which will next time read the UserDetails based on token, not user credentials. 因此,您想生成一个令牌,将其存储在数据库(或缓存,或令牌存储,或会话存储)中,然后实现另一个AuthenticationProvider ,它将下次基于令牌而不是用户凭据读取UserDetails

You could use a JWT token which actually is able to serialize your whole UserDetails object into the token itself & send it over the wire, but I wouldn't recommend that either as there is no way to invalidate it, unless you're storing the token id in your database / session store. 您可以使用JWT令牌,该令牌实际上能够将整个UserDetails对象序列化为令牌本身并通过电线发送,但我不建议这样做,因为没有办法使它无效,除非您存储了数据库/会话存储中的令牌id

More to read here: Invalidating JSON Web Tokens 在此处阅读更多内容:使JSON Web令牌无效

So it turns out it was a simple matter of extending WebAuthenticationDetails , like so: 事实证明,扩展WebAuthenticationDetails很简单,就像这样:

public class JwtAuthenticationDetails extends WebAuthenticationDetails {

    private static final long serialVersionUID = 6951476359238150556L;
    private String token ;

    public JwtAuthenticationDetails(HttpServletRequest request) {
        super(request);
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }

    @Override
    public String toString() {
        return "JwtAuthenticationDetails [token=" + token + "; " + super.toString() + "]";
    }
}

And then to register that in my WebSecurityConfigurerAdapter : 然后在我的WebSecurityConfigurerAdapter注册它:

private AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationDetails> getAuthenticationDetailsSource() {
    // TODO Auto-generated method stub
    return new AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationDetails>() {

        @Override
        public JwtAuthenticationDetails buildDetails(HttpServletRequest context) {
            return new JwtAuthenticationDetails(context);

        }

    } ;
}

Finally, 最后,

in my RestAuthenticationProvider , I can store the token in the authentication details: 在我的RestAuthenticationProvider ,我可以将令牌存储在身份验证详细信息中:

@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {

    String password = (String) authentication.getCredentials();
    String token = getToken(authentication.getName(), password);

    JwtAuthenticationDetails details = (JwtAuthenticationDetails) authentication.getDetails() ;
    details.setToken(token);
    Collection<GrantedAuthority> authorities = getAuthorities(token);
    return new User(authentication.getName(), password, authorities);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM