【发布时间】:2016-02-20 11:12:45
【问题描述】:
我正在使用最新版本的 Spring Boot,并且正在尝试设置 StatelessAuthenticaion。到目前为止,我一直在阅读的教程非常模糊,我不确定我做错了什么。我正在使用的教程是...
http://technicalrex.com/2015/02/20/stateless-authentication-with-spring-security-and-jwt/
我的设置的问题是,除了 TokenAuthenticationService::addAuthentication 从未被调用因此我的令牌从未设置,因此它在调用 TokenAuthenticationService::getAuthentication 时返回 null 并因此返回 401即使我成功登录(因为永远不会调用 addAuthentication 来设置标头中的令牌)。我试图想办法添加TokenAuthenticationService::addAuthentication,但我觉得这很困难。
在教程中,他将类似于WebSecurityConfig::UserDetailsService.userService 的内容添加到auth.userDetailsService() 中。我遇到的唯一问题是,当我这样做时,它会引发 CastingErrorException。它仅在我使用 UserDetailsService customUserDetailsService 时才有效...
WebSecurityConfig
package app.config;
import app.repo.User.CustomUserDetailsService;
import app.security.RESTAuthenticationEntryPoint;
import app.security.RESTAuthenticationFailureHandler;
import app.security.RESTAuthenticationSuccessHandler;
import app.security.TokenAuthenticationService;
import app.security.filters.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static PasswordEncoder encoder;
private final TokenAuthenticationService tokenAuthenticationService;
private final CustomUserDetailsService userService;
@Autowired
private UserDetailsService customUserDetailsService;
@Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
public WebSecurityConfig() {
this.userService = new CustomUserDetailsService();
tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userService);
}
@Autowired
public void configureAuth(AuthenticationManagerBuilder auth,DataSource dataSource) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated();
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().defaultSuccessUrl("/").successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
//This is ho
http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
@Bean
@Override
public CustomUserDetailsService userDetailsService() {
return userService;
}
@Bean
public TokenAuthenticationService tokenAuthenticationService() {
return tokenAuthenticationService;
}
}
TokenAuthenticationService 成功调用了getAuthentication 方法,但在我阅读的教程中,没有正确解释如何调用addAuthentication
令牌认证服务
package app.security;
import app.repo.User.CustomUserDetailsService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TokenAuthenticationService {
private static final String AUTH_HEADER_NAME = "X-AUTH-TOKEN";
private final TokenHandler tokenHandler;
//This is called in my WebSecurityConfig() constructor
public TokenAuthenticationService(String secret, CustomUserDetailsService userService) {
tokenHandler = new TokenHandler(secret, userService);
}
public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {
final UserDetails user = authentication.getDetails();
response.addHeader(AUTH_HEADER_NAME, tokenHandler.createTokenForUser(user));
}
public Authentication getAuthentication(HttpServletRequest request) {
final String token = request.getHeader(AUTH_HEADER_NAME);
if (token != null) {
final UserDetails user = tokenHandler.parseUserFromToken(token);
if (user != null) {
return new UserAuthentication(user);
}
}
return null;
}
}
令牌处理程序
package app.security;
import app.repo.User.CustomUserDetailsService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
public final class TokenHandler {
private final String secret;
private final CustomUserDetailsService userService;
public TokenHandler(String secret, CustomUserDetailsService userService) {
this.secret = secret;
this.userService = userService;
}
public UserDetails parseUserFromToken(String token) {
String username = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
return userService.loadUserByUsername(username);
}
public String createTokenForUser(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
}
在我的 WebServiceConfig 中。我添加以下内容
http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
它调用以下类作为过滤器。它获得了身份验证,但没有实际添加它的位置。
StatelessAuthenticationFilter
package app.security.filters;
import app.security.TokenAuthenticationService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by anthonygordon on 11/17/15.
*/
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService authenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
Authentication authentication = authenticationService.getAuthentication(httpRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.getContext().setAuthentication(null);
}
}
以下类是在TokenAuthenticationService::addAuthentication 中传递的内容
用户认证
package app.security;
import app.repo.User.User;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class UserAuthentication implements Authentication {
private final UserDetails user;
private boolean authenticated = true;
public UserAuthentication(UserDetails user) {
this.user = user;
}
@Override
public String getName() {
return user.getUsername();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public Object getCredentials() {
return user.getPassword();
}
@Override
public UserDetails getDetails() {
return user;
}
@Override
public Object getPrincipal() {
return user.getUsername();
}
@Override
public boolean isAuthenticated() {
return authenticated;
}
@Override
public void setAuthenticated(boolean authenticated) {
this.authenticated = authenticated;
}
}
就是这样……
我的解决方案(但需要帮助)...
我的解决方案是在我的成功处理程序中设置TokenAuthenticationService::addAuthentication 方法...唯一的问题是教程将TokenAuthenticationService 类添加到WebServiceConfig 类中。那是它唯一可以访问的地方。如果有办法在我的 successHandler 中获取它,我也许可以设置令牌。
package app.security;
import app.controllers.Requests.TriviaResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by anthonygordon on 11/12/15.
*/
@Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
TriviaResponse tresponse = new TriviaResponse();
tresponse.setMessage("You have successfully logged in");
String json = ow.writeValueAsString(tresponse);
response.getWriter().write(json);
clearAuthenticationAttributes(request);
}
}
【问题讨论】:
标签: java spring-boot restful-authentication jwt