$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
AIコーディングエージェント(NotebookLM)
kondai24
0
210
S3 VectorsとStrands Agentsを利用したAgentic RAGシステムの構築
tosuri13
6
340
FluorTracer / RayTracingCamp11
kugimasa
0
240
ELYZA_Findy AI Engineering Summit登壇資料_AIコーディング時代に「ちゃんと」やること_toB LLMプロダクト開発舞台裏_20251216
elyza
2
270
JETLS.jl ─ A New Language Server for Julia
abap34
1
420
WebRTC、 綺麗に見るか滑らかに見るか
sublimer
1
190
Graviton と Nitro と私
maroon1st
0
110
非同期処理の迷宮を抜ける: 初学者がつまづく構造的な原因
pd1xx
1
730
複数人でのCLI/Infrastructure as Codeの暮らしを良くする
shmokmt
5
2.3k
ViewファーストなRailsアプリ開発のたのしさ
sugiwe
0
500
20251212 AI 時代的 Legacy Code 營救術 2025 WebConf
mouson
0
190
バックエンドエンジニアによる Amebaブログ K8s 基盤への CronJobの導入・運用経験
sunabig
0
160
Featured
See All Featured
Done Done
chrislema
186
16k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.1k
Typedesign – Prime Four
hannesfritz
42
2.9k
Navigating Team Friction
lara
191
16k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
34k
Side Projects
sachag
455
43k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
249
1.3M
Agile that works and the tools we love
rasmusluckow
331
21k
The Pragmatic Product Professional
lauravandoore
37
7.1k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
Build The Right Thing And Hit Your Dates
maggiecrowley
38
3k
Balancing Empowerment & Direction
lara
5
810
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
͓ΘΓ