私は休息APIを持っており、私は春のセキュリティの基本的な認証を使用して認証している、クライアントは、各リクエストにユーザー名とパスワードを送信します。 今、私はトークンベースの認証を実装したいと思い、最初にユーザーが認証されたときにレスポンスヘッダにトークンを送信します。それ以降のリクエストでは、クライアントはそのトークンをヘッダーに含めることができ、そのトークンがリソースに対するユーザーの認証に使われます。私は2つの認証プロバイダtokenAuthenticationProviderとdaoAuthenticationProviderを持っています。
@Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
@Autowired
private TokenAuthentcationService service;
@Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
final String token = request.getHeader(Constants.AUTH_HEADER_NAME);
final Token tokenObj = this.service.getToken(token);
final AuthenticationToken authToken = new AuthenticationToken(tokenObj);
return authToken;
}
@Override
public boolean supports(final Class<?> authentication) {
return AuthenticationToken.class.isAssignableFrom(authentication);
}
}
そして、daoAuthenticationProviderでは、カスタムuserDetailsServiceを設定し、データベースから取得することでユーザーのログイン詳細に対して認証を行っています(ユーザー名とパスワードがAuthorization:Basic bGllQXBpVXNlcjogN21wXidMQjRdTURtR04pag== としてヘッダーで渡されれば正常に動作します)。
しかし、X-AUTH-TOKEN(Constants.AUTH_HEADER_NAME)を使用してヘッダーにトークンを含めると、tokenAuthenticationProviderは呼び出されません。私は次のようなエラーを受け取っています。
{"timestamp":1487626368308,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/find"}
また、認証プロバイダを追加する方法は以下の通りです。
@Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
final UsernamePasswordAuthenticationProvider daoProvider = new
UsernamePasswordAuthenticationProvider(this.service, this.passwordEncoder());
auth.authenticationProvider(this.tokenAuthenticationProvider);
auth.authenticationProvider(daoProvider);
}
Spring Securityの現在の動作を損なうことなく、Tokenベースの認証を実装するにはどうしたらよいか、提案してください。
トークンベース認証とベーシック認証を実装することができたのは以下の通りです。
SpringSecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
@Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(this.participantService).passwordEncoder(this.passwordEncoder());
}
@Override
protected void configure(final HttpSecurity http) throws Exception
{
//Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
//Creating token when basic authentication is successful and the same token can be used to authenticate for further requests
final CustomBasicAuthenticationFilter customBasicAuthFilter = new CustomBasicAuthenticationFilter(this.authenticationManager() );
http.addFilter(customBasicAuthFilter);
}
}
TokenAuthenticationFilter.java(トークン認証フィルター)。
public class TokenAuthenticationFilter extends GenericFilterBean
{
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException
{
final HttpServletRequest httpRequest = (HttpServletRequest)request;
//extract token from header
final String accessToken = httpRequest.getHeader("header-name");
if (null != accessToken) {
//get and check whether token is valid ( from DB or file wherever you are storing the token)
//Populate SecurityContextHolder by fetching relevant information using token
final User user = new User(
"username",
"password",
true,
true,
true,
true,
authorities);
final UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
CustomBasicAuthenticationFilter.java
@Component
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
public CustomBasicAuthenticationFilter(final AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void onSuccessfulAuthentication(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response, final Authentication authResult) {
//Generate Token
//Save the token for the logged in user
//send token in the response
response.setHeader("header-name" , "token");
}
}
CustomBasicAuthenticationFilterが設定され、spring securityのフィルタとして追加されたので、
ベーシック認証が成功すると、リクエストは onSuccessfulAuthentication にリダイレクトされ、トークンをセットしてレスポンスに "header-name" というヘッダを付けて送信されます。
もし"header-name"がさらなるリクエストに送られた場合、リクエストはベーシック認証を試みる前にまずTokenAuthenticationFilterを通過することになります。
例えば、認証フィルターに独自の AuthenticationToken
トークンを設定してみてください:
public class AuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final String authTokenHeader = ((HttpServletRequest)request).getHeader(Constants.AUTH_HEADER_NAME);
if (authTokenHeader != null) {
SecurityContextHolder.getContext().setAuthentication(createAuthenticationToken(authTokenHeader));
}
chain.doFilter( request, response );
}
}