$30 off During Our Annual Pro Sale. View Details »
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
340
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
380
決済サービスのSpring Bootのバージョンを2系に上げた話
b1a9id
0
150
パラレルキャリアがもたらす相乗効果
b1a9id
1
1.3k
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
b1a9id
0
1.7k
ユニットテストのアサーション 流れるようなインターフェースのAssertJを添えて 入門者仕立て
b1a9id
1
140
Spring超入門-Springと出会ってから1年半-
b1a9id
1
81
Spring starterによるSpring Boot Starter
b1a9id
1
80
Other Decks in Programming
See All in Programming
複数人でのCLI/Infrastructure as Codeの暮らしを良くする
shmokmt
5
2.2k
AIコーディングエージェント(skywork)
kondai24
0
150
LLMで複雑な検索条件アセットから脱却する!! 生成的検索インタフェースの設計論
po3rin
2
650
tsgolintはいかにしてtypescript-goの非公開APIを呼び出しているのか
syumai
6
2.1k
Cap'n Webについて
yusukebe
0
120
React Native New Architecture 移行実践報告
taminif
1
150
Socio-Technical Evolution: Growing an Architecture and Its Organization for Fast Flow
cer
PRO
0
320
手が足りない!兼業データエンジニアに必要だったアーキテクチャと立ち回り
zinkosuke
0
590
ViewファーストなRailsアプリ開発のたのしさ
sugiwe
0
430
ゲームの物理 剛体編
fadis
0
320
配送計画の均等化機能を提供する取り組みについて(⽩⾦鉱業 Meetup Vol.21@六本⽊(数理最適化編))
izu_nori
0
140
【CA.ai #3】ワークフローから見直すAIエージェント — 必要な場面と“選ばない”判断
satoaoaka
0
230
Featured
See All Featured
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
253
22k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.7k
Mobile First: as difficult as doing things right
swwweet
225
10k
Facilitating Awesome Meetings
lara
57
6.7k
Building Flexible Design Systems
yeseniaperezcruz
330
39k
The Cult of Friendly URLs
andyhume
79
6.7k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.1k
Why Our Code Smells
bkeepers
PRO
340
57k
Code Reviewing Like a Champion
maltzj
527
40k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
390
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
15k
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
͓ΘΓ