Springには、…ContextHolderというクラスがある。スレッドローカルに値を保存しておくことで、情報をいろんなところから参照できるようにする。
スレッドローカルは…まあスレッド固有の値ですよね。(あたりまえ)
TomcatなどのAPサーバはリクエストごとにworkerスレッドを割り当てるので、
Java ブロッキングとかノンブロッキングを理解したい - SIerだけど技術やりたいブログ
@AsyncやExecutorServiceを利用して別スレッドでXXXContextHolderを利用しない限り、どこからでもスレッドローカルに登録した同じ値を参照できる。具体的なクラスは、
Spring MVC | RequestContextHolder | リクエスト情報 |
Spring Security | SecurityContextHolder | 認証情報 |
使い方
Spring Bootでリクエストと認証のログを出してみる。spring-boot-starter-securityがclasspathに含まれてるとデフォルトでDIGEST認証がかかるので、今回は認証にそれを使う。
ログ処理はHandlerInterceptorを使う。
引数にHttpServletRequestが取れるけど、わざとRequestContextHolderから取得する。
@Slf4j public class LoggingInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String msg = (String) request.getParameter("msg"); log.info("request : " + msg); SecurityContext sc = SecurityContextHolder.getContext(); Authentication authentication = sc.getAuthentication(); log.info("authentication : " + authentication); return true; } }
HandlerInterceptorを登録する。
@Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter{ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoggingInterceptor()) .addPathPatterns("/**"); } }
リクエストするためのメソッドを用意する。
@RestController @SpringBootApplication public class ContextholderApplication { public static void main(String[] args) { SpringApplication.run(ContextholderApplication.class, args); } @GetMapping(value = "sample", params = "msg") public String param() { return "success"; } }
起動するとログに認証パスワードが表示されるので、それを使ってリクエストする。
... 2017-02-18 19:39:47.925 INFO 13184 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2017-02-18 19:39:47.955 INFO 13184 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2017-02-18 19:39:48.227 INFO 13184 --- [ main] b.a.s.AuthenticationManagerConfiguration : Using default security password: e5ad05bf-3daa-4a1b-91ad-81d726ed712a
curl --user user:e5ad05bf-3daa-4a1b-91ad-81d726ed712a http://localhost:8080/sample?msg=hello
ログに出る。
2017-02-18 19:40:01.634 INFO 13184 --- [nio-8080-exec-1] com.example.LoggingInterceptor : request : hello 2017-02-18 19:40:01.634 INFO 13184 --- [nio-8080-exec-1] com.example.LoggingInterceptor : authentication : org.springframework.security.authentication.UsernamePasswordAuthenticationToken@442b5a9f: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_USER
うん。
・・・うん。