Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Amazon Cognito使って認証したい?それならSpring Security使いましょう!

Amazon Cognito使って認証したい?それならSpring Security使いましょう!

Spring Fest 2018 #sf_23 #jsug

Ryosuke Uchitate

October 31, 2018
Tweet

More Decks by Ryosuke Uchitate

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ w ໊લ w ಺ཱྑհʢ͏ͪͨͯΓΐ͏͚͢ʣ w झຯ w ༸෰ w

    ύϯ԰८Γ w ॴଐ w ίΠχʔגࣜձࣾ w ++6( w 'BTIJPO$IBSJUZ1SPKFDU w 5XJUUFS w !CBJEQT
  2. ΞδΣϯμ  ࠓճͷΰʔϧ 4QSJOH4FDVSJUZͱ͸ʁ " 4QSJOH4FDVSJUZ֓ཁ # ೝূॲཧͷ࢓૊Έ $ ೝՄॲཧͷ࢓૊Έ

     '03.ೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZ8JUI"NB[PO$PHOJUP  $PHOJUPೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZͷςετ
  3. ΞδΣϯμ  ࠓճͷΰʔϧ 4QSJOH4FDVSJUZͱ͸ʁ " 4QSJOH4FDVSJUZ֓ཁ # ೝূॲཧͷ࢓૊Έ $ ೝՄॲཧͷ࢓૊Έ

     '03.ೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZ8JUI"NB[PO$PHOJUP  $PHOJUPೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZͷςετ
  4. 4FDVSJUZ'JMUFSͷॱং final class FilterComparator implements Comparator<Filter>, Serializable { private static

    final int INITIAL_ORDER = 100; private static final int ORDER_STEP = 100; private final Map<String, Integer> filterToOrder = new HashMap<>(); FilterComparator() { Step order = new FilterComparator.Step(INITIAL_ORDER, ORDER_STEP); put(ChannelProcessingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); put(WebAsyncManagerIntegrationFilter.class, order.next()); put(SecurityContextPersistenceFilter.class, order.next()); put(HeaderWriterFilter.class, order.next()); put(CorsFilter.class, order.next()); put(CsrfFilter.class, order.next()); put(LogoutFilter.class, order.next()); // …… 'JMUFS$PNQBSBUPSKBWB
  5. ΞδΣϯμ  ࠓճͷΰʔϧ 4QSJOH4FDVSJUZͱ͸ʁ " 4QSJOH4FDVSJUZ֓ཁ # ೝূॲཧͷ࢓૊Έ $ ೝՄॲཧͷ࢓૊Έ

     '03.ೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZ8JUI"NB[PO$PHOJUP  $PHOJUPೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZͷςετ
  6. ೝূͷཁૉ આ໌ ݻ༗৘ใ ੜମ৘ใ ੜ෺ݻ༗ͷ৘ใɾಛੑ ࢦ໲ɺ੩຺ɺ إɺ੠໲ ஌ࣝ৘ใ ຊਓ͔͠ ஌Βͳ͍৘ใ

    ʢϫϯλΠϜʣύεϫʔυɺ ൿີͷ࣭໰ ॴ࣋৘ใ ຊਓ͔࣋ͬͯ͠ͳ͍ ৘ใ΍෺ *$Χʔυɺ ϋʔυ΢ΣΞτʔΫϯ
  7. ΞδΣϯμ  ࠓճͷΰʔϧ 4QSJOH4FDVSJUZͱ͸ʁ " 4QSJOH4FDVSJUZ֓ཁ # ೝূॲཧͷ࢓૊Έ $ ೝՄॲཧͷ࢓૊Έ

     '03.ೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZ8JUI"NB[PO$PHOJUP  $PHOJUPೝূ࣮૷ͯ͠Έͨ  4QSJOH4FDVSJUZͷςετ
  8. "DDFTT%FDJTJPO7PUFSKBWB w ΞΫηεݖͷ෇༩͢Δ͔Λ౤ථ͢Δɻ w 8FC&YQSFTTJPO7PUFSKBWB͕σϑΥϧτద༻ɻ public interface AccessDecisionVoter<S> { int

    ACCESS_GRANTED = 1; int ACCESS_ABSTAIN = 0; int ACCESS_DENIED = -1; (3"/5&%ɿࢍ੒ "#45"*/ɿغ٫ʢ࣍ͷ7PUFS΁ʣ %&/*&%ɿڋ൱
  9. 8FC4FDVSJUZ$POpHKBWB @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private

    final UserDetailsServiceImpl userDetailsService; // …… @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/js/**", "/css/**", "/webjars/**").permitAll() .antMatchers("/users/**").hasRole(Role.STAFF.name()) .antMatchers("/**").authenticated() .and() .formLogin() .loginPage("/login") .loginProcessingUrl("/login") .defaultSuccessUrl("/success", true) .failureUrl("/login?error=true").permitAll(); } }
  10. 8FC4FDVSJUZ$POpHKBWB @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private

    final UserDetailsServiceImpl userDetailsService; // …… @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/js/**", "/css/**", "/webjars/**").permitAll() .antMatchers("/users/**").hasRole(Role.STAFF.name()) .antMatchers("/**").authenticated() .and() .formLogin() .loginPage("/login") .loginProcessingUrl("/login") .defaultSuccessUrl("/success", true) .failureUrl("/login?error=true").permitAll(); } } ୭Ͱ΋ΞΫηεՄ KT  DTT  XFCKBST 45"''ϩʔϧͷΈΞΫηεՄ VTFST ೝূϢʔβͷΈΞΫηεՄ  '03.ೝূͷઃఆ ϩά ΠϯϖʔδɿMPHJO ࢿ֨৘ใ1045ઌɿMPHJO ೝূ੒ޭޙͷϦμΠϨΫτઌɿTVDDFTF ೝূࣦഊޙͷϦμΠϨΫτઌɿMPHJO FSSPSUSVF
  11. 6TFSOBNF1BTTXPSE"VUIFOUJDBUJPO'JMUFSBUUFNQU"VUIFOUJDBUJPO public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response) throws AuthenticationException

    { // …… String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
  12. 6TFSOBNF1BTTXPSE"VUIFOUJDBUJPO'JMUFSBUUFNQU"VUIFOUJDBUJPO public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response) throws AuthenticationException

    { // …… String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
  13. 1SPWJEFS.BOBHFSBVUIFOUJDBUF public Authentication authenticate(Authentication authentication) throws AuthenticationException { // ……

    for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } // …… try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } // ……
  14. %BP"VUIFOUJDBUJPO1SPWJEFSSFUSJFWF6TFS protected final UserDetails retrieveUser( String username, UsernamePasswordAuthenticationToken authentication) throws

    AuthenticationException { // …… try { UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { // …… } return loadedUser; } // …… } Ϣʔβ৘ใΛऔಘ
  15. 6TFS%FUBJMT4FSWJDF*NQMKBWB @Service @RequiredArgsConstructor public class UserDetailsServiceImpl implements UserDetailsService { private

    final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow( () -> new UsernameNotFoundException("username not found")); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), createAuthorityList("ROLE_" + user.getRole().name())); } } ࢿ֨৘ใͱϢʔβͷঢ়ଶΛσʔλετΞ͔Βऔಘ͢Δ
  16. %BP"VUIFOUJDBUJPO1SPWJEFSBEEJUJPOBM"VUIFOUJDBUJPO$IFDLT protected void additionalAuthenticationChecks( UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException

    { // …… String presentedPassword = authentication.getCredentials().toString(); if (!passwordEncoder.matches( presentedPassword, userDetails.getPassword())) { // …… throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } ύεϫʔυͷൺֱ
  17. $VTUPN1SF"VUIFOUJDBUFE1SPDFTTJOH'JMUFSKBWB public class CustomPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter { @Override protected Object

    getPreAuthenticatedPrincipal( HttpServletRequest request) { return ""; } @Override protected Object getPreAuthenticatedCredentials( HttpServletRequest request) { String accessToken = request.getHeader(HttpHeaders.AUTHORIZATION); if (StringUtils.isEmpty(accessToken) || !accessToken.startsWith("Bearer ")) { return ""; } return accessToken.split(" ")[1]; } }
  18. $VTUPN1SF"VUIFOUJDBUFE1SPDFTTJOH'JMUFSKBWB public class CustomPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter { @Override protected Object

    getPreAuthenticatedPrincipal( HttpServletRequest request) { return ""; } @Override protected Object getPreAuthenticatedCredentials( HttpServletRequest request) { String accessToken = request.getHeader(HttpHeaders.AUTHORIZATION); if (StringUtils.isEmpty(accessToken) || !accessToken.startsWith("Bearer ")) { return ""; } return accessToken.split(" ")[1]; } }
  19. "CTUSBDU1SF"VUIFOUJDBUFE1SPDFTTJOH'JMUFSEP"VUIFOUJDBUF private void doAuthenticate( HttpServletRequest request, HttpServletResponse response) throws IOException,

    ServletException { Authentication authResult; Object principal = getPreAuthenticatedPrincipal(request); Object credentials = getPreAuthenticatedCredentials(request); // …… try { PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken( principal, credentials); authRequest.setDetails( authenticationDetailsSource.buildDetails(request)); authResult = authenticationManager.authenticate(authRequest); successfulAuthentication(request, response, authResult); } catch (AuthenticationException failed) { // …… } } $VTUPN1SF"VUIFOUJDBUFE1SPDFTTJOH'JMUFSͰ ΦʔόʔϥΠυ
  20. "CTUSBDU1SF"VUIFOUJDBUFE1SPDFTTJOH'JMUFSEP"VUIFOUJDBUF private void doAuthenticate( HttpServletRequest request, HttpServletResponse response) throws IOException,

    ServletException { Authentication authResult; Object principal = getPreAuthenticatedPrincipal(request); Object credentials = getPreAuthenticatedCredentials(request); // …… try { PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken( principal, credentials); // …… authResult = authenticationManager.authenticate(authRequest); successfulAuthentication(request, response, authResult); } catch (AuthenticationException failed) { // …… } }
  21. 6TFS"DDFTT5PLFO"VUIFOUJDBUJPO1SPWJEFSBVUIFOUJDBUF public Authentication authenticate(Authentication auth) throws AuthenticationException { String accessToken

    = Optional.ofNullable(auth.getCredentials()) .map(Object::toString) .orElse(null); if (accessToken == null) { throw new BadCredentialsException("access token not found."); } DecodedJWT decodedAccessToken = JWTUtils.decode(accessToken); // …… ΞΫηετʔΫϯͷݕূ String username = decodedAccessToken.getClaim("username").asString(); UserDetails ud = userDetailsService.loadUserDetails( new PreAuthenticatedAuthenticationToken( username, auth.getCredentials()); return new PreAuthenticatedAuthenticationToken( ud, authentication.getCredentials(), ud.getAuthorities()); }
  22. 6TFS"DDFTT5PLFO"VUIFOUJDBUJPO1SPWJEFSBVUIFOUJDBUF public Authentication authenticate(Authentication auth) throws AuthenticationException { String accessToken

    = Optional.ofNullable(auth.getCredentials()) .map(Object::toString) .orElse(null); if (accessToken == null) { throw new BadCredentialsException("access token not found."); } DecodedJWT decodedAccessToken = JWTUtils.decode(accessToken); // …… ΞΫηετʔΫϯͷݕূ String username = decodedAccessToken.getClaim("username").asString(); UserDetails ud = userDetailsService.loadUserDetails( new PreAuthenticatedAuthenticationToken( username, auth.getCredentials()); return new PreAuthenticatedAuthenticationToken( ud, authentication.getCredentials(), ud.getAuthorities()); } ΞΫηετʔΫϯͷݕূ +85ͷߏ଄Λ֬ೝ͢Δɻ +85ॺ໊Λݕূ͢Δɻ ΫϨʔϜΛݕূ͢Δɻ
  23. 6TFS"DDFTT5PLFO"VUIFOUJDBUJPO1SPWJEFSBVUIFOUJDBUF public Authentication authenticate(Authentication auth) throws AuthenticationException { String accessToken

    = Optional.ofNullable(auth.getCredentials()) .map(Object::toString) .orElse(null); if (accessToken == null) { throw new BadCredentialsException("access token not found."); } DecodedJWT decodedAccessToken = JWTUtils.decode(accessToken); // …… JWTͷݕূ String username = decodedAccessToken.getClaim("username").asString(); UserDetails ud = userDetailsService.loadUserDetails( new PreAuthenticatedAuthenticationToken( username, auth.getCredentials()); return new PreAuthenticatedAuthenticationToken( ud, authentication.getCredentials(), ud.getAuthorities()); }
  24. $VTUPN"VUIFOUJDBUJPO6TFS%FUBJMT4FSWJDFKBWB @Service public class CustomAuthenticationUserDetailsService implements AuthenticationUserDetailsService { private final

    CustomUserDetailsService userDetailsService; // …… @Override public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException { String username = token.getPrincipal().toString(); String accessToken = token.getCredentials().toString(); return Optional.ofNullable( userDetailsService.loadUserByUsername(username)) .map(u -> new CustomUserDetails( ((CustomUserDetails) u).getUser(), accessToken)) .orElseThrow(() -> new UsernameNotFoundException("user not found")); } }
  25. '03.ೝূͷςετ @Test void loginSuccess() throws Exception { MvcResult result =

    mockMvc .perform(formLogin() .user("ruchitate").password("password")) .andReturn(); Assertions.assertThat(result.getResponse()) .extracting( MockHttpServletResponse::getStatus, MockHttpServletResponse::getRedirectedUrl) .containsExactly(302, "/success"); } 4FDVSJUZ.PDL.WD3FRVFTU#VJEFSTGPSN-PHJO ϩά ΠϯϢʔβ Λઃఆ
  26. 30-&Ͱύε੍ݶͰ͖ͯΔ͔ͷςετ @Test void useWith200() throws Exception { MvcResult result =

    mockMvc.perform(get("/users/{id}", 1) .with(user("ruchitate").roles("STAFF"))) .andExpect(status().isOk()) .andReturn(); assertEquals( "{\"name\":\"಺ཱ ྑհ\",\"username\":\"ruchitate\", \"createdAt\":\"2018-10-01T00:00:00\",\"lastSignInAt\":null}", result.getResponse().getContentAsString()); }
  27. !1SF"VUIPSJ[F @PreAuthorize("hasRole('ADMIN')") public List<User> list() { return userRepository.findAll(); } @PreAuthorize("#role

    == 'ADMIN'") public List<User> list(String role) { return userRepository.findAll(); } @PreAuthorize("#r.name == 'ruchitate'") public List<User> list(@P("r") UserRequest request) { return userRepository.findAll(); } ϝιουʹೖΔલʹνΣοΫॲཧΛߦ͏ɻ GBMTFͷͱ͖ɺ"DDFTT%FOJFE&YDFQUJPOΛεϩʔ
  28. !1PTU"VUIPSJ[F @PostAuthorize("returnObject != null && returnObject.username == 'ruchitate'") public User

    get(Integer id) { return userRepository.findById(id).orElse(null); } ϝιου௨աޙʹ໭Γ஋ʹରͯ͠νΣοΫॲཧΛߦ͏ɻ GBMTFͷͱ͖ɺ"DDFTT%FOJFE&YDFQUJPOΛεϩʔ
  29. !1SF'JMUFS @PreFilter("filterObject.name.equals('ruchitate')") public List<User> list(List<UserRequest> requests) { List<String> usernameList =

    requests.stream() .map(UserRequest::getName) .collect(Collectors.toList()); return userRepository .findAllByUsernameIn(usernameList); } ϝιουʹೖΔલʹҾ਺ͷதͰ৚݅ʹҰக͢ΔΦϒδΣ ΫτͷΈநग़ɻ