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
320
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
キャッシュレス決済のプロダクトから決済基盤への進化
b1a9id
0
310
決済サービスのSpring Bootのバージョンを2系に上げた話
b1a9id
0
150
パラレルキャリアがもたらす相乗効果
b1a9id
1
1.3k
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
b1a9id
0
1.6k
ユニットテストのアサーション 流れるようなインターフェースのAssertJを添えて 入門者仕立て
b1a9id
1
130
Spring超入門-Springと出会ってから1年半-
b1a9id
1
80
Spring starterによるSpring Boot Starter
b1a9id
1
77
Other Decks in Programming
See All in Programming
AIコーディングエージェント全社導入とセキュリティ対策
hikaruegashira
16
9.4k
AIに安心して任せるためにTypeScriptで一意な型を作ろう
arfes0e2b3c
0
330
PHPUnitの限界をPlaywrightで補完するテストアプローチ
yuzneri
0
380
11年かかって やっとVibe Codingに 時代が追いつきましたね
yimajo
1
240
ソフトウェア設計とAI技術の活用
masuda220
PRO
25
7.3k
iOS開発スターターキットの作り方
akidon0000
0
230
TypeScriptでDXを上げろ! Hono編
yusukebe
4
930
書き捨てではなく継続開発可能なコードをAIコーディングエージェントで書くために意識していること
shuyakinjo
0
190
[Codecon - 2025] Como não odiar seus testes
camilacampos
0
100
あまり知られていない MCP 仕様たち / MCP specifications that aren’t widely known
ktr_0731
0
220
PHPカンファレンス関西2025 基調講演
sugimotokei
6
1.1k
リッチエディターを安全に開発・運用するために
unachang113
1
350
Featured
See All Featured
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
26k
Building Applications with DynamoDB
mza
95
6.5k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
Why Our Code Smells
bkeepers
PRO
337
57k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
50
5.5k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.8k
Adopting Sorbet at Scale
ufuk
77
9.5k
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.6k
The Cost Of JavaScript in 2023
addyosmani
51
8.7k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
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
͓ΘΓ