Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Form認証で学ぶSpring Security入門
Search
Ryosuke Uchitate
August 28, 2019
Programming
0
270
Form認証で学ぶSpring Security入門
JSUG勉強会 2019その8 Spring for Beginner #jsug
Ryosuke Uchitate
August 28, 2019
Tweet
Share
More Decks by Ryosuke Uchitate
See All by Ryosuke Uchitate
決済サービスのSpring Bootのバージョンを2系に上げた話
b1a9id
0
140
パラレルキャリアがもたらす相乗効果
b1a9id
1
1.2k
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
b1a9id
0
1.5k
ユニットテストのアサーション 流れるようなインターフェースのAssertJを添えて 入門者仕立て
b1a9id
1
110
Spring超入門-Springと出会ってから1年半-
b1a9id
1
73
Spring starterによるSpring Boot Starter
b1a9id
1
71
Other Decks in Programming
See All in Programming
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
hirobe1999
0
480
コンテナをたくさん詰め込んだシステムとランタイムの変化
makihiro
1
130
なまけものオバケたち -PHP 8.4 に入った新機能の紹介-
tanakahisateru
1
120
CQRS+ES の力を使って効果を感じる / Feel the effects of using the power of CQRS+ES
seike460
PRO
0
130
SymfonyCon Vienna 2025: Twig, still relevant in 2025?
fabpot
3
1.2k
Jakarta EE meets AI
ivargrimstad
0
240
return文におけるstd::moveについて
onihusube
1
1.1k
開発者とQAの越境で自動テストが増える開発プロセスを実現する
92thunder
1
180
今年一番支援させていただいたのは認証系サービスでした
satoshi256kbyte
1
250
責務を分離するための例外設計 - PHPカンファレンス 2024
kajitack
6
890
テストコード書いてみませんか?
onopon
2
110
アクターシステムに頼らずEvent Sourcingする方法について
j5ik2o
4
270
Featured
See All Featured
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
2
290
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
28
2.1k
How to train your dragon (web standard)
notwaldorf
88
5.7k
Making Projects Easy
brettharned
116
5.9k
It's Worth the Effort
3n
183
28k
Rails Girls Zürich Keynote
gr2m
94
13k
BBQ
matthewcrist
85
9.4k
StorybookのUI Testing Handbookを読んだ
zakiyama
27
5.3k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
48k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
A better future with KSS
kneath
238
17k
Transcript
'PSNೝূͰֶͿ 4QSJOH4FDVSJUZೖ JSUGษڧձͦͷ̔ ίΠχʔגࣜձࣾɹཱྑհ !b1a9idps
ࣗݾհ ໊લ ཱྑհʢ͏ͪͨͯΓΐ͏͚͢ʣ ॴଐ ίΠχʔגࣜձࣾ 'BTIJPO$IBSJUZ1SPKFDU ࠷ۙͷࣄ 4QSJOH#PPUYͷόʔδϣϯΞοϓ
ΞδΣϯμ w 4QSJOH4FDVSJUZ֓ཁ w ࠓճ࡞Δͷ w '03.ೝূ w 3PMF)JFSBSDIZ w
(MPCBM.FUIPE4FDVSJUZ w 4QSJOH4FDVSJUZͷςετ
ΞδΣϯμ w4QSJOH4FDVSJUZ֓ཁ w ࠓճ࡞Δͷ w '03.ೝূ w 3PMF)JFSBSDIZ w (MPCBM.FUIPE4FDVSJUZ
w 4QSJOH4FDVSJUZͷςετ
ొ͢ΔओͳϞδϡʔϧ܈
ϑϨʔϜϫʔΫͷΞʔΩςΫνϟ ΫϥΠΞϯτ͔ΒϦΫΤετ͕ૹΒΕͯ͘Δ 'JMUFS$IBJO1SPYZKBWB͕ϦΫΤετड͚औΔ )UUQ4FSWMFU3FRVFTUͱ)UUQ4FSWMFU3FTQPOTFʹରͯ͠ϑΝΠΞΥʔϧػೳ ΛΈࠐΉ 4FDVSJUZ'JMUFS$IBJOʹઃఆ͞Ε͍ͯΔ4FDVSJUZ'JMUFSʹॲཧΛҕৡ͢Δ
ϑϨʔϜϫʔΫͷΞʔΩςΫνϟ 4FDVSJUZ'JMUFS͕ॱʹݺͼग़͞ΕͯॲཧΛߦ͏ શ4FDVSJUZ'JMUFSͷॲཧ͕ਖ਼ৗऴྃͨ͠Βɺ8FCΞϓϦέʔγϣϯϦιʔε ΞΫηεͰ͖Δ
4FDVSJUZ'JMUFSͷྫ w -PHPVU'JMUFSKBWB w ϩάΞτॲཧΛߦ͏ w 6TFSOBNF1BTTXPSE"VUIFOUJDBUJPO'JMUFSKBWB w 'PSNೝূͰೝূॲཧΛߦ͏ w
#BTJD"VUIFOUJDBUJPO'JMUFSKBWB w ϕʔγοΫೝূͰೝূॲཧΛߦ͏ w &YDFQUJPO5SBOTMBUJPO'JMUFSKBWB w ೝՄॲཧͰൃੜͨ͠ྫ֎ΛϋϯυϦϯά͠ɺΫϥΠΞϯτ దͳϨεϙϯεΛߦ͏
ΞδΣϯμ w 4QSJOH4FDVSJUZ֓ཁ wࠓճ࡞Δͷ w '03.ೝূ w 3PMF)JFSBSDIZ w (MPCBM.FUIPE4FDVSJUZ
w 4QSJOH4FDVSJUZͷςετ
ϢʔβཧγεςϜ ϢʔβҰཡɺৄࡉɺొɺআ
༷ w ϖʔδͷࢀরʹೝূ͕ඞਢ w ݖݶɺ08/&3."/"(&345"'' ొ Ұཡɾৄࡉ আ 08/&3 Մ
Մ Մ ."/"(&3 Մ MANAGERͱSTAFF ͷΈӾཡՄ ෆՄ 45"'' ෆՄ STAFFͷΈӾཡՄ ෆՄ
ΞδΣϯμ w 4QSJOH4FDVSJUZ֓ཁ w ࠓճ࡞Δͷ w'03.ೝূ w 3PMF)JFSBSDIZ w (MPCBM.FUIPE4FDVSJUZ
w 4QSJOH4FDVSJUZͷςετ
ೝূͱೝՄ ೝূ w ର͕ɺ୭ʢԿʣͰ͋Δ͔Λ֬ೝ͢Δ͜ͱ ೝՄ w ߦಈϦιʔεͷΞΫηεΛڐՄ͢Δ͜ͱ
࣮͢ΔΫϥε w 8FC4FDVSJUZ$POpHKBWB w 4QSJOH4FDVSJUZ༻ͷઃఆ w "VUIFOUJDBUFE6TFSKBWB w 6TFS%FUBJMTΠϯλʔϑΣʔεͷ࣮ɻೝূࡁͷϢʔβใ w
6TFS%FUBJMT.BOBHFSKBWB w 6TFS%FUBJMT4FSWJDFΠϯλʔϑΣʔεͷ࣮ɻࢿ֨ใͱϢʔβͷঢ়ଶΛ σʔλετΞ͔Βऔಘ
8FC4FDVSJUZ$POpHKBWB @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private
final UserDetailsManager userDetailsManager; public WebSecurityConfig(UserDetailsManager userDetailsManager) { this.userDetailsManager = userDetailsManager; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/users/create").hasRole("OWNER", "MANAGER") .antMatchers("/users/delete/{id}").hasRole("OWNER") .anyRequest().authenticated() .and().formLogin().loginPage("/login").defaultSuccessUrl("/users", true) .and().logout().logoutSuccessUrl("/login").permitAll() .and().csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsManager) .passwordEncoder(passwordEncoder()); } }
8FC4FDVSJUZ$POpHKBWBʢղઆʣ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests()
// "/users/create"OWNERͱMANAGERݖݶͷΈϦΫΤετՄ .antMatchers("/users/create").hasAnyRole("OWNER", "MANAGER") // "/users/delete/{id}"OWNERͱMANAGERݖݶͷΈϦΫΤετՄ .antMatchers(“/users/delete/{id}").hasRole("OWNER") // ೝূࡁΈϢʔβͷΈ͕ϦΫΤετՄ .anyRequest().authenticated() // FORMೝূΛ༗ޮʹ͢ΔɺϩάΠϯϖʔδͷύε"/login"ɺϩάΠϯޭޙ"/users"ʹϦμΠ ϨΫτ .and().formLogin().loginPage("/login").defaultSuccessUrl("/users", true) // ϩάΞτޭޙͷϦμΠϨΫτઌ"/login" .and().logout().logoutSuccessUrl("/login").permitAll() // CSRFରࡦػೳΛແޮʹ͢Δ .and().csrf().disable(); }
)UUQ4FDVSJUZGPSN-PHJO ͬͯԿͯ͠ΔΜ͚ͩͬʁ
'PSN-PHJO$POpHVSFSKBWB public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>,
UsernamePasswordAuthenticationFilter> { public FormLoginConfigurer() { // UsernamePasswordAuthenticationFilterΛAuthenticationFilterʹઃఆ super(new UsernamePasswordAuthenticationFilter(), null); // Formͷusernameύϥϝʔλ໊Λ"username"ʹ usernameParameter(“username"); // Formͷpasswordύϥϝʔλ໊Λ"password"ʹ passwordParameter("password"); } 'PSN-PHJO$POpHVSFSKBWBΛOFXͯ͠ '03.ೝূΛ༗ޮʹ͢Δ
8FC4FDVSJUZ$POpHKBWBʢղઆʣ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //
ೝূ࣌ʹ͏UserDetailsService.javaͷ࣮Λઃఆɺར༻͢ΔPasswordEncoder.javaΛઃఆ auth.userDetailsService(userDetailsManager) .passwordEncoder(passwordEncoder()); }
ϑΥʔϜೝূͷྲྀΕ Ϣʔβ໊ͱύεϫʔυʢࢿ֨ใʣΛϦΫΤετύϥϝʔλͱͯ͠1045 6TFSOBNF1BTTXPSE"VUIFOUJDBUJPO'JMUFSͰࢿ֨ใΛऔಘ "VUIFOUJDBUJPO.BOBHFSʹೝূॲཧΛҕৡ
6TFSOBNF1BTTXPSE"VUIFOUJDBUJPO'JMUFSKBWB public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } // ϦΫΤετύϥϝʔλ͔ΒೖྗΛநग़ 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); // AuthenticationManagerʹࢿ֨ใΛ͢ return this.getAuthenticationManager().authenticate(authRequest); }
ϑΥʔϜೝূͷྲྀΕ ొ͞Ε͍ͯΔ"VUIFOUJDBUJPO1SPWJEFSΛॱʹݺͿ
ϑΥʔϜೝূͷྲྀΕ ࢿ֨ใ͕ਖ਼͍͔֬͠ೝΛߦ͏
ϑΥʔϜೝূͷྲྀΕ ϢʔβใΛऔಘ͢Δɻࠓճ%#͔Βऔಘ͢Δɻ
%BP"VUIFOUJDBUJPO1SPWJEFSKBWB protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
{ prepareTimingAttackProtection(); try { // ϢʔβใΛऔಘ UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; } catch (UsernameNotFoundException ex) { mitigateAgainstTimingAttack(authentication); throw ex; } catch (InternalAuthenticationServiceException ex) { throw ex; } catch (Exception ex) { throw new InternalAuthenticationServiceException(ex.getMessage(), ex); } }
%BP"VUIFOUJDBUJPO1SPWJEFSKBWB protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } String presentedPassword = authentication.getCredentials().toString(); // ύεϫʔυͷൺֱ if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug( "Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials”)); } }
6TFS%FUBJMT.BOBHFSKBWB @Service public class UserDetailsManager implements UserDetailsService { private final
UserRepository userRepository; public UserDetailsManager(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username) .map(AuthenticatedUser::new) .orElseThrow( () -> new UsernameNotFoundException("username not found")); } }
"VUIFOUJDBUFE6TFSKBWB public class AuthenticatedUser implements UserDetails { private final Integer
id; private final String name; private final String username; private final String password; private final Role role; // ίϯετϥΫλলུ public Integer getId() { return id; } public String getName() { return name; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return createAuthorityList("ROLE_" + role.name()); } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
ϑΥʔϜೝূͷྲྀΕ ೝূ͕ޭͨ͠Βɺ4BWFE3FRVFTU"XBSF"VUIFOUJDBUJPO4VDDFTT)BOEMFS ͕ݺΕͯೝূޭ࣌ͷॲཧޙʹTVDDFTT6SMʹϦμΠϨΫτ ೝূ͕ࣦഊͨ͠Βɺ4JNQMF6SM"VUIFOUJDBUJPO'BJMVSF)BOEMFS͕ݺΕͯೝূ ࣦഊ࣌ͷॲཧޙʹGBJMVSF6SMʹϦμΠϨΫτ
ิ w ೝূใηογϣϯͰཧ w 4FTTJPO$POUFYU1FSTJTUFODF'JMUFSKBWBͰطʹೝূࡁΈ͔νΣοΫ w ೝূࣦഊͨ͠Ϣʔβೝূใ࡞ΒΕΔ w σϑΥϧτͰ3PMF͕30-&@"/0/:.064ʹͳΔ
ೝՄॲཧͷྲྀΕ 4QSJOH4FDVSJUZͰى͖ͨྫ֎ΛϋϯυϦϯάͯ͠దͳϨεϙϯεΛߦ͏ ະೝূϢʔβ͔ΒͷΞΫηεͷ߹ೝূΛଅ͢ϨεϙϯεΛฦ͠ɺೝূࡁ Ϣʔβ͔ΒͷΞΫηεͷ߹ೝՄΤϥʔΛ௨͢ΔϨεϙϯεΛฦ͢
ೝՄॲཧͷྲྀΕ )551ϦΫΤετʹରͯ͠ೝՄॲཧΛద༻͢Δ ೝՄॲཧ"DDFTT%FDJTJPO.BOBHFSʹҕৡ
ೝՄॲཧͷྲྀΕ "DDFTT%FDJTJPO.BOBHFS͕ొ͞Εͯ͋Δ"DDFTT%FDJTJPO7PUFSΛݺͿ "DDFTT%FDJTJPO7PUFSͰΞΫηεݖͷ༗ແΛථ͢Δ
"DDFTT%FDJTJPO7PUFSKBWB w ΞΫηεݖΛ༩͢Δ͔Λථ͢Δ w 8FC&YQSFTTJPO7PUFSKBWB͕σϑΥϧτద༻ public interface AccessDecisionVoter<S> { int
ACCESS_GRANTED = 1; int ACCESS_ABSTAIN = 0; int ACCESS_DENIED = -1;
"DDFTT%FDJTJPO.BOBHFSKBWB w ථ݁Ռ͔Β࠷ऴతͳΞΫηεݖΛஅ w "DDFTT%FDJTJPO7PUFSΛݺΜͰΞΫηεݖΛ ථͯ͠Β͏ w ࣮̏Ϋϥεɻථ݁Ռͷѻ͍ํ͕ҟͳΔɻ "⒏SNBUJWF#BTFEKBWBɺ$POTFOTVT#BTFEKBWBɺ 6OBOJNPVT#BTFEKBWB
"⒏SNBUJWF#BTFEKBWB શ7PUFSͷ͏ͪ̍ͭͰࢍ͢ΕΞΫηεڐՄ
$POTFOTVT#BTFEKBWB શ7PUFSͰࢍ͕ଟ͚ΕΞΫηεڐՄ
6OBOJNPVT#BTFEKBWB શ7PUFS͕ࢍͳΒΞΫηεڐՄ
ೝՄॲཧͷྲྀΕ ೝՄޭͷ߹ͷΈϦιʔεΞΫηεͰ͖ΔɻೝՄࣦഊͷ߹ɺ "DDFTT%FOJFE&YDFQUJPO͕͛ΒΕΔɻ
ʹΜ͔͠ΐΓͷ͘͠Έ ථ݁Ռͷѻ͍ํΛܾΊΔ ථ͢Δ
ೝূɾೝՄͷྲྀΕΛཧղ͓͔ͯ͠ͳ͍ͱ w 4QSJOH4FDVSJUZ8BZʹ࣮͕ͬͨͰ͖ͳ͍ w 'JMUFS͚ͩͰೝূॲཧͪ͠Ό͏Έ͍ͨͳμα͍͜ͱʹ ͳΔ
ΞδΣϯμ w 4QSJOH4FDVSJUZ֓ཁ w ࠓճ࡞Δͷ w '03.ೝূ w3PMF)JFSBSDIZ w (MPCBM.FUIPE4FDVSJUZ
w 4QSJOH4FDVSJUZͷςετ
3PMF)JFSBSDIZ w 3PMFʹ֊Λ࣋ͨͤΔ͜ͱ w Լͷ3PMFؚΜͰ͍ΔͱΈͳ͢ w 08/&3."/"(&3ͷ߹ɺ08/&3."/"(&3͕Ͱ͖Δ͜ͱશͯ Ͱ͖Δ
࣮͢ΔΫϥε w 8FC4FDVSJUZ$POpHKBWB w 4FDVSJUZ3PMFT1SPQFSUJFTKBWB w 3PMF)JFSBSDIZʹؔ͢ΔઃఆΛ࣋ͭΫϥε
8FC4FDVSJUZ$POpHKBWB @Bean public RoleHierarchy roleHierarchy(SecurityRolesProperties rolesProperties) { return rolesProperties.getRoleHierarchy(); }
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/users/create").hasRole("MANAGER") .antMatchers("/users/delete/{id}").hasRole("OWNER") .anyRequest().authenticated() .and() .formLogin().loginPage("/login").defaultSuccessUrl("/users", true) .and() .logout().logoutSuccessUrl("/login").permitAll() .and() .csrf().disable(); }
8FC4FDVSJUZ$POpHKBWBʢղઆʣ @Bean public RoleHierarchy roleHierarchy(SecurityRolesProperties rolesProperties) { // RoleHierarchyImpl.javaΛBeanొ return
rolesProperties.getRoleHierarchy(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // ԼͷRoleΛؚΈ࣋ͭͷͰMANAGERͷΈͰΑ͍ .antMatchers("/users/create").hasRole("MANAGER") .antMatchers("/users/delete/{id}").hasRole("OWNER") .anyRequest().authenticated() .and() .formLogin().loginPage("/login").defaultSuccessUrl("/users", true) .and() .logout().logoutSuccessUrl("/login").permitAll() .and() .csrf().disable(); }
4FDVSJUZ3PMFT1SPQFSUJFTKBWB @Component @ConfigurationProperties("security.roles") public class SecurityRolesProperties { private Map<String, List<String>>
hierarchyMap = new LinkedHashMap<>(); public Map<String, List<String>> getHierarchyMap() { return hierarchyMap; } public void setHierarchyMap(Map<String, List<String>> hierarchyMap) { this.hierarchyMap = hierarchyMap; } public RoleHierarchy getRoleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); String hierarchy = isEmpty(hierarchyMap) ? "" : roleHierarchyFromMap(hierarchyMap); roleHierarchy.setHierarchy(hierarchy); return roleHierarchy; } }
4FDVSJUZ3PMF1SPQFSUJFTKBWBʢղઆʣ @Component @ConfigurationProperties("security.roles") public class SecurityRolesProperties { private Map<String, List<String>>
hierarchyMap = new LinkedHashMap<>(); public Map<String, List<String>> getHierarchyMap() { return hierarchyMap; } public void setHierarchyMap(Map<String, List<String>> hierarchyMap) { this.hierarchyMap = hierarchyMap; } public RoleHierarchy getRoleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); String hierarchy = isEmpty(hierarchyMap) ? "" : roleHierarchyFromMap(hierarchyMap); // HierarchyΛจࣈྻͰηοτ͢Δ // hierarchy = "ROLE_OWNER > ROLE_MANAGER\nROLE_MANAGER > ROLE_STAFF\n" roleHierarchy.setHierarchy(hierarchy); return roleHierarchy; } }
ΞδΣϯμ w 4QSJOH4FDVSJUZ֓ཁ w ࠓճ࡞Δͷ w '03.ೝূ w 3PMF)JFSBSDIZ w(MPCBM.FUIPE4FDVSJUZ
w 4QSJOH4FDVSJUZͷςετ
(MPCBM.FUIPE4FDVSJUZ w ΞϊςʔγϣϯΛͬͯɺϝιουϨϕϧͰΞΫη ε੍ޚ͕Ͱ͖Δɻछྨ̐ͭɻ w αʔϏεͰͷར༻Λਪ͍ͯ͠Δ w ϝιου࣮ߦલͱޙʹॲཧΛೖΕΔ͜ͱ͕Ͱ͖Δ w !1SF999ϝιουͷॲཧલʹɺ!1PTU999ϝ
ιουͷॲཧޙʹΞΫηε੍ޚ͕ߦΘΕΔ
!1SF"VUIPSJ[F // ϩάΠϯϢʔβͷRole͕OWNERͷͱ͖ϝιουʹೖΕΔ @PreAuthorize("hasRole('OWNER')") public List<User> list() { return userRepository.findAll();
} // Ҿrole͕"OWNER"ʹҰக͢Δͱ͖ϝιουʹೖΕΔ @PreAuthorize("#role == 'OWNER'") public List<User> list(String role) { return userRepository.findAll(); } // Ҿrequest.name͕"ruchitate"ʹҰக͢Δͱ͖ϝιουʹೖΕΔ @PreAuthorize("#r.name == 'ruchitate'") public List<User> list(@P("r") UserRequest request) { return userRepository.findAll(); } ϝιουΛݺΔݖݶΛ͍࣋ͬͯΔ͔
!1PTU"VUIPSJ[F // Γͷม໊σϑΥϧτͰreturnObject @PostAuthorize("returnObject != null && returnObject.username == 'ruchitate'")
public User get(Integer id) { return userRepository.findById(id).orElse(null); } औಘͰ͖ΔݖݶΛ͍࣋ͬͯΔ͔
!1SF'JMUFS // Ҿͷม໊σϑΥϧτͰfilterObject @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); } ҾͷதͰ݅ʹҰக͢ΔΦϒδΣΫτͷΈநग़
!1PTU'JMUFS // Γͷม໊σϑΥϧτͰfilterObject @PostFilter("filterObject.username == 'ruchitate'") public List<User> list() {
return userRepository.findAll(); } ݅ʹҰக͢ΔΦϒδΣΫτΛΓ͔Βநग़
ࠓճͷαϯϓϧʹద༻͢Δ @Override // Beanొͨ͠Ϋϥε͑Δ @PostFilter("@roleEvaluator.hasRole(principal, filterObject.role)") public List<UserDto> findAll() {
List<UserDto> userDtoList = new ArrayList<>(); userRepository.findAll().iterator() .forEachRemaining(user -> userDtoList.add(UserDto.newUserDto(user))); return userDtoList; } @Override @PostAuthorize("returnObject != null && @roleEvaluator.hasRole(principal, returnObject.role)") public UserDto findOne(Integer id) { return userRepository.findById(id) .map(UserDto::newUserDto) .get(); }
ࠓճͷαϯϓϧʹద༻͢Δ @Override // MANAGER or OWNER @PreAuthorize("hasRole('MANAGER')") public UserDto create(UserCreateForm
form) { User user = new User(); BeanUtils.copyProperties(form, user, "password"); user.setPassword(passwordEncoder.encode(form.getPassword())); return UserDto.newUserDto(userRepository.save(user)); } @Override @PreAuthorize("hasRole('OWNER')") public void delete(Integer id) { User user = userRepository.findById(id) .filter(u -> u.getRole() != Role.OWNER) .orElseThrow(NotAllowedOperationException::new); userRepository.delete(user); }
ΞδΣϯμ w 4QSJOH4FDVSJUZ֓ཁ w ࠓճ࡞Δͷ w '03.ೝূ w 3PMF)JFSBSDIZ w
(MPCBM.FUIPE4FDVSJUZ w4QSJOH4FDVSJUZͷςετ
ґଘؔʹՃ dependencies { testImplementation 'org.springframework.security:spring-security-test' } <dependencies> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId>
<scope>test</scope> </dependency> </dependencies>
6TFS$POUSPMMFS5FTUKBWB @Nested @SpringBootTest class ListTest { @Autowired private WebApplicationContext context;
private MockMvc mockMvc; @BeforeEach void beforeEach() { // SecurityFilterΛՃ mockMvc = webAppContextSetup(context).apply(springSecurity()).build(); } @Test void success() throws Exception { User user = new User(); user.setId(1); user.setName("ཱྑհ"); user.setUsername("ruchitate"); user.setPassword("12345678"); user.setRole(Role.OWNER); mockMvc.perform(get("/users") // ΞΫηε͢ΔϢʔβʔ .with(user(new AuthenticatedUser(user)))) .andExpect(status().isOk()); } }
6TFS4FSWJDF*NQM5FTUKBWB @Test // ΞΫηε͢ΔϢʔβ @WithUserDetails(value = "yaragaki", userDetailsServiceBeanName = "userDetailsManager")
void findAllForManager() { List<UserDto> result = userService.findAll(); // ࣗͷRoleҎԼͷϢʔβͷΈࢀরՄೳ Assertions.assertThat(result) .extracting( UserDto::getId, UserDto::getName, UserDto::getAge, UserDto::getGender, UserDto::getRole) .containsExactly( Tuple.tuple(2, "৽֞ɹ݁ҥ", 31, WOMAN, MANAGER), Tuple.tuple(3, "ࢁ࡚ɹݡਓ", 24, MAN, STAFF)); }
6TFS4FSWJDF*NQM5FTUKBWB @Test @WithUserDetails(value = "ruchitate", userDetailsServiceBeanName = "userDetailsManager") void deleteForOwner()
{ userService.delete(2); Assertions.assertThatThrownBy(() -> userService.findOne(2)) .isInstanceOf(NotFoundException.class); } @Test @WithUserDetails(value = "yaragaki", userDetailsServiceBeanName = "userDetailsManager") void deleteForManager() { Assertions.assertThatThrownBy(() -> userService.delete(3)) .isInstanceOf(AccessDeniedException.class); } 08/&3ͷΈআՄ
'03.ೝূͷςετ @Test void loginSuccess() throws Exception { MvcResult result //
ϩάΠϯϢʔβΛઃఆ = mockMvc.perform(formLogin().user("ruchitate").password("12345678")) .andReturn(); Assertions.assertThat(result.getResponse()) .extracting( MockHttpServletResponse::getStatus, MockHttpServletResponse::getRedirectedUrl) .containsExactly(HttpStatus.FOUND.value(), "/users"); } @Test void loginFailed() throws Exception { MvcResult result = mockMvc.perform(formLogin().user("ruchitate").password("test")) .andReturn(); Assertions.assertThat(result.getResponse()) .extracting( MockHttpServletResponse::getStatus, MockHttpServletResponse::getRedirectedUrl) .containsExactly(HttpStatus.FOUND.value(), "/login?error"); }
ࠓճհͨ͠αϯϓϧ IUUQTHJUIVCDPNCBJETQSJOH TFDVSJUZTBNQMFKTVH
͓ΘΓ