前提条件
以下URLのプログラムを元にJwt認証用(ステートレス)に変更します。
クラスの説明
以下の6つのクラスを改修、追加します。
◇設定ファイル
〇WebSecurityConfigクラス(設定)
◇認証(トークン取得)
〇WebUsernamePasswordAuthenticationFilterクラス(認証)
〇WebAuthenticationProviderクラス(検証)
〇SuccessHandlerクラス(認証検証成功時)
〇FailureHandlerクラス(認証検証失敗時)
◇認可(トークン認可)
〇JwtAuthorizationFilterクラス(認可)
プログラムの流れ
◇JWTトークン取得する場合
ユーザがトークン取得APIを実行時
↓
認証(トークン取得)が動く
◇その他APIにアクセスする場合
ユーザがトークン取得API以外のその他API実行時
↓
認可(トークン認可)が動く
設定ファイル
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* HTTP Security設定
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();
http.addFilter(webUsernamePasswordAuthenticationFilter(authenticationManagerBean())).addFilter(jwtAuthorizationFilter(authenticationManagerBean()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/**
* 独自認証を設定
*/
@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
@Autowired
private WebAuthenticationProvider webAuthenticationProvider;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(webAuthenticationProvider);
}
}
/**
* 認証フィルター
* @param authenticationManager
* @return
*/
public WebUsernamePasswordAuthenticationFilter webUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
// 独自フィルター作成
WebUsernamePasswordAuthenticationFilter filter = new WebUsernamePasswordAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/v1/api/auth", "GET")); // ログイン時URL
filter.setAuthenticationSuccessHandler(successHandler()); // 成功時URL
filter.setAuthenticationFailureHandler(failureHandler()); // 失敗時URL
//filter.setUsernameParameter("username"); // ユーザ パラメータ名
//filter.setPasswordParameter("password"); // パスワード パラメータ名
return filter;
}
/**
* 認証成功時処理
* @return
*/
@Bean
public SuccessHandler successHandler() {
return new SuccessHandler();
}
/**
* 認証失敗時処理
* @return
*/
@Bean
public FailureHandler failureHandler() {
return new FailureHandler();
}
/**
* 認可フィルター
* @param authenticationManager
* @return
*/
public JwtAuthorizationFilter jwtAuthorizationFilter(AuthenticationManager authenticationManager) {
JwtAuthorizationFilter filter = new JwtAuthorizationFilter(authenticationManager);
return filter;
}
}
独自の認証、認可フィルター作成し、認証成功、失敗のHandler
を設定している。
public class WebUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
/**
* 独自フィルター
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
String authorization = req.getHeader("Authorization");
String userAndPass = new String(Base64.decodeBase64(authorization.substring("Basic".length())));
String user = userAndPass.substring(0,userAndPass.indexOf(":"));
String password = userAndPass.substring((userAndPass.indexOf(":")+1));
if(user == null) {
user = "";
}
if(password == null) {
password = "";
}
// トークンの作成
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user, password);
setDetails(req, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
認証のフィルタークラスではヘッダーからBasic認証でデータを取得するよう作成している。
@Configuration
@EnableWebSecurity
public class WebAuthenticationProvider implements AuthenticationProvider {
/**
* 認証処理
*/
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String user = auth.getPrincipal().toString();
String password = auth.getCredentials().toString();
if (ObjectUtils.isEmpty(user)) {
throw new AuthenticationCredentialsNotFoundException("ユーザー名もしくはパスワードに誤りがあります。");
}
if (ObjectUtils.isEmpty(password)) {
throw new AuthenticationCredentialsNotFoundException("ユーザー名もしくはパスワードに誤りがあります。");
}
if (!user.equals("user")) {
throw new AuthenticationCredentialsNotFoundException("ユーザー名もしくはパスワードに誤りがあります。");
}
if (!password.equals("pass")) {
throw new AuthenticationCredentialsNotFoundException("ユーザー名もしくはパスワードに誤りがあります。");
}
Collection<GrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
// トークンを返却
return new UsernamePasswordAuthenticationToken(user, password,authorityList);
}
@Override
public boolean supports(Class<?> token) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(token);
}
}
Filterでセットしたログイン情報をもとに認証。
public class SuccessHandler implements AuthenticationSuccessHandler {
/**
* 認証成功時
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
String secretKey = "secretKey";
Date now = new Date();
String user = (String)auth.getPrincipal();
Calendar cal = Calendar.getInstance();
cal.setTime(now);
cal.add(Calendar.MINUTE, 60);
Date expTime = cal.getTime();
String token = JWT.create()
.withSubject(user)
.withIssuedAt((now))
.withExpiresAt(expTime)
.sign(Algorithm.HMAC256(secretKey.getBytes()));
response.setStatus(HttpStatus.OK.value());
response.addHeader("Authrization","Bearer "+ token);
}
}
認証成功時にトークン作成しresponse
にセットし返却。
public class FailureHandler implements AuthenticationFailureHandler {
/**
* 認証失敗時処理
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
失敗時は401エラーで返却。
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
/**
* コンストラクタ
* @param authenticationManager
*/
public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* Jwtの認可処理
*/
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String token = req.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
chain.doFilter(req, res);
return;
}
// 認証トークン作成
UsernamePasswordAuthenticationToken authentication = getAuthentication(token);
// 独自に認可
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
/**
* 認証token作成
* @param token
* @return
*/
private UsernamePasswordAuthenticationToken getAuthentication(String token) {
if (token != null) {
try {
String secretKey = "secretKey";
String user = JWT.require(Algorithm.HMAC256(secretKey.getBytes()))
.build()
.verify(StringUtils.substringAfter(token, "Bearer "))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
}catch(Exception e) {
logger.error("error",e);
}
return null;
}
return null;
}
}
受け取ったトークンが正しいかをチェック。
実装の仕方はいろんな方法があると思うので今後も調査していきます!