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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
400
決済サービスのSpring Bootのバージョンを2系に上げた話
b1a9id
0
160
パラレルキャリアがもたらす相乗効果
b1a9id
1
1.3k
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
b1a9id
0
1.7k
ユニットテストのアサーション 流れるようなインターフェースのAssertJを添えて 入門者仕立て
b1a9id
1
140
Spring超入門-Springと出会ってから1年半-
b1a9id
1
85
Spring starterによるSpring Boot Starter
b1a9id
1
85
Other Decks in Programming
See All in Programming
Sekiban + Microsoft Orleans のアクターをAWS対応しました / Sekiban + Microsoft Orleans actors are now supported on AWS.
tomohisa
0
110
humanlayerのブログから学ぶ、良いCLAUDE.mdの書き方
tsukamoto1783
0
200
開発者から情シスまで - 多様なユーザー層に届けるAPI提供戦略 / Postman API Night Okinawa 2026 Winter
tasshi
0
230
責任感のあるCloudWatchアラームを設計しよう
akihisaikeda
3
190
2026年 エンジニアリング自己学習法
yumechi
0
150
Gemini for developers
meteatamel
0
110
CSC307 Lecture 06
javiergs
PRO
0
700
AI Schema Enrichment for your Oracle AI Database
thatjeffsmith
0
360
あなたはユーザーではない #PdENight
kajitack
3
150
Data-Centric Kaggle
isax1015
2
810
AgentCoreとHuman in the Loop
har1101
5
270
Oxlint JS plugins
kazupon
1
1.1k
Featured
See All Featured
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
1
130
Facilitating Awesome Meetings
lara
57
6.8k
Bash Introduction
62gerente
615
210k
Documentation Writing (for coders)
carmenintech
77
5.3k
The Power of CSS Pseudo Elements
geoffreycrofte
80
6.2k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
120
How to Ace a Technical Interview
jacobian
281
24k
Testing 201, or: Great Expectations
jmmastey
46
8.1k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
58
50k
We Have a Design System, Now What?
morganepeng
54
8k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
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
͓ΘΓ