Search

์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ

์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ

Spring Security 6

์ด์ „ ํŽ˜์ด์ง€

์ด์ „ ํŽ˜์ด์ง€ ๋‚ด์šฉ์— ์ด์–ด์„œ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

Code

Preview

1.
๋กœ๊ทธ์ธ ํ™”๋ฉด
2.
๋ฉ”์ธ ํ™”๋ฉด

์ž‘์—… ํ”„๋กœ์„ธ์Šค

1.
ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
2.
ํ”„๋กœ์ ํŠธ ์„ค์ •
3.
๋„๋ฉ”์ธ
4.
์„œ๋น„์Šค
5.
์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •

Preview

๋กœ๊ทธ์ธ ํ™”๋ฉด

๋ฉ”์ธ ํ™”๋ฉด

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

build.gradle

spring boot 3.x.x
spring security 6.x.x
plugins { id 'java' id 'war' id 'org.springframework.boot' version '3.3.5' id 'io.spring.dependency-management' version '1.1.6' } group = 'com.aloha' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
Java
๋ณต์‚ฌ

ํ”„๋กœ์ ํŠธ ์„ค์ •

application.properties

spring.application.name=login # ๋ฐ์ดํ„ฐ ์†Œ์Šค - MySQL spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/aloha?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false&autoReconnection=true&autoReconnection=true spring.datasource.username=aloha spring.datasource.password=123456 # Mybatis ์„ค์ • mybatis.configuration.map-underscore-to-camel-case=true mybatis.type-aliases-package=com.aloha.login.domain mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
Markdown
๋ณต์‚ฌ

๋„๋ฉ”์ธ

โ€ข
Users.java
โ€ข
UserAuth.java
โ€ข
CustomUser.java

Users.java

@Data public class Users { private Long no; private String id; private String username; private String password; private String name; private String email; private Date createdAt; private Date updatedAt; private int enabled; private List<UserAuth> authList; }
Java
๋ณต์‚ฌ

UserAuth.java

@Data public class UserAuth { private Long no; private String username; private String auth; }
Java
๋ณต์‚ฌ

CustomUser.java

DB์—์„œ ์กฐํšŒํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์ •์˜ํ•œ ์‚ฌ์šฉ์ž ๋ฐ ๊ถŒํ•œ Users, UserAuth ๊ฐ์ฒด๋กœ ๋งคํ•‘ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ์‚ฌ์šฉ์ž ์ •๋ณด ์ธํ„ฐํŽ˜์ด์Šค์ธ UserDetails ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ๊ฐ์ฒด๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
@Getter @ToString public class CustomUser implements UserDetails { // ์‚ฌ์šฉ์ž DTO private Users user; public CustomUser(Users user) { this.user = user; } /** * ๐Ÿ” ๊ถŒํ•œ ์ •๋ณด ๋ฉ”์†Œ๋“œ * โœ… UserDetails ๋ฅผ CustomUser ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ, * Spring Security ์˜ User ๋Œ€์‹  ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ๊ฐ์ฒด(CustomUser)๋กœ ์ ์šฉ * โš  CustomUser ์ ์šฉ ์‹œ, ๊ถŒํ•œ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” 'ROLE_' ๋ถ™์—ฌ์„œ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค. */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return user.getAuthList().stream() .map( (auth) -> new SimpleGrantedAuthority(auth.getAuth()) ) .collect(Collectors.toList()); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return user.getEnabled() == 0 ? false : true; } }
Java
๋ณต์‚ฌ

์„œ๋น„์Šค

โ€ข
UserDetailServiceImpl.java

UserDetailServiceImpl.java

UserDetailsService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ, ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
/** * ๐Ÿ” UserDetailsService : ์‚ฌ์šฉ์ž ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ธํ„ฐํŽ˜์ด์Šค * โœ… ์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ, ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. */ @Slf4j @Service public class UserDetailServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info(":::::::::: UserDetailServiceImpl ::::::::::"); log.info("- ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ์„ ์œ„ํ•ด, ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ"); log.info("- username : " + username); Users user = null; try { // ๐Ÿ‘ฉโ€๐Ÿ’ผ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ ๊ถŒํ•œ ์กฐํšŒ user = userMapper.select(username); } catch (Exception e) { e.printStackTrace(); } if( user == null ) { throw new UsernameNotFoundException("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + username); } // ๐Ÿ” CustomUser โžก UserDetails CustomUser customUser = new CustomUser(user); return customUser; } }
Java
๋ณต์‚ฌ

Mapper

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.aloha.login.mapper.UserMapper"> <resultMap type="Users" id="userMap"> <id property="no" column="no" /> <result property="no" column="no" /> <result property="id" column="id" /> <result property="username" column="username" /> <result property="password" column="password" /> <result property="name" column="name" /> <result property="email" column="email" /> <result property="enabled" column="enabled" /> <result property="createdAt" column="created_at" /> <result property="updatedAt" column="updated_at" /> <collection property="authList" resultMap="authMap"></collection> </resultMap> <resultMap type="UserAuth" id="authMap"> <result property="no" column="no" /> <result property="username" column="username" /> <result property="auth" column="auth" /> </resultMap> <!-- ํšŒ์› ์กฐํšŒ - id --> <select id="select" resultMap="userMap"> SELECT u.no ,u.username ,password ,name ,email ,enabled ,created_at ,updated_at ,auth FROM user u LEFT OUTER JOIN user_auth auth ON u.username = auth.username WHERE u.username = #{username} </select> <!-- ํšŒ์› ๊ฐ€์ž… --> <insert id="join"> INSERT INTO user ( username, password, name, email ) VALUES ( #{username}, #{password}, #{name}, #{email} ) </insert> <!-- ํšŒ์› ์ •๋ณด ์ˆ˜์ • --> <update id="update"> UPDATE user SET name = #{name} ,email = #{email} ,updated_at = now() WHERE username = #{username} </update> <!-- ํšŒ์› ๊ถŒํ•œ ๋“ฑ๋ก --> <insert id="insertAuth"> INSERT INTO user_auth( username, auth ) VALUES ( #{username}, #{auth} ) </insert> </mapper>
XML
๋ณต์‚ฌ

UserMapper.java

@Mapper public interface UserMapper { // ํšŒ์› ์กฐํšŒ public Users select(String id) throws Exception; // ํšŒ์› ๊ฐ€์ž… public int join(Users user) throws Exception; // ํšŒ์› ์ˆ˜์ • public int update(Users user) throws Exception; // ํšŒ์› ๊ถŒํ•œ ๋“ฑ๋ก public int insertAuth(UserAuth userAuth) throws Exception; }
Java
๋ณต์‚ฌ

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •

โ€ข
SecurityConfig.java

SecurityConfig.java

UserDetailsService ๋นˆ์„ ๋ฉ”์†Œ๋“œ๋กœ ์ •์˜ํ•˜๋Š” ๋ฐฉ์‹์—์„œ, @Service ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ์„ค์ •์„ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜ํ•œ๋‹ค.

์ด์ „ ๋ฐฉ์‹ (AS-IS)

์ ์šฉ ๋ฐฉ์‹ (TO-BE)

โ€ข
UserDetailServiceImpl ์˜์กด์„ฑ ์ฃผ์ž…
โ€ข
์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ์„ค์ •

UserDetailServiceImpl ์˜์กด์„ฑ ์ฃผ์ž…

@Autowired private UserDetailServiceImpl userDetailServiceImpl;
Java
๋ณต์‚ฌ

์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ์„ค์ •

// โœ… ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ์„ค์ • http.userDetailsService(userDetailServiceImpl);
Java
๋ณต์‚ฌ

SecurityConfig.java

@Slf4j @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) //์–ด๋…ธํ…Œ์ด์…˜์— prePostEnabled = true๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด AuthenticationManager๋ฅผ ์ž๋™์œผ๋กœ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. public class SecurityConfig { @Autowired private CustomUserDetailService customUserDetailService; @Autowired private JwtTokenProvider jwtTokenProvider; private AuthenticationManager authenticationManager; @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { this.authenticationManager = authenticationConfiguration.getAuthenticationManager(); return authenticationManager; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { log.info("securityFilterChain..."); // ํผ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™” http.formLogin( login -> login.disable() ); // HTTP ๊ธฐ๋ณธ ์ธ์ฆ ๋น„ํ™œ์„ฑํ™” http.httpBasic( basic -> basic.disable() ); // CSRF(Cross-Site Request Forgery) ๊ณต๊ฒฉ ๋ฐฉ์–ด ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™” http.csrf( csrf -> csrf.disable() ); // ํ•„ํ„ฐ ์„ค์ • // โœ… JWT ์š”์ฒญ ํ•„ํ„ฐ 1๏ธโƒฃ // โœ… JWT ์ธ์ฆ ํ•„ํ„ฐ 2๏ธโƒฃ http.addFilterAt(new JwtAuthenticationFilter(authenticationManager, jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JwtRequestFilter(authenticationManager, jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) ; // ์ธ๊ฐ€ ์„ค์ • http.authorizeHttpRequests() .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() .antMatchers("/").permitAll() .antMatchers("/login").permitAll() .antMatchers("/user/**").hasAnyRole("USER" , "ADMIN") .antMatchers("/admin/**").hasRole("ADMIN") // .anyRequest().authenticated() ; // ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์„œ๋น„์Šค ์„ค์ • http.userDetailsService(customUserDetailService); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
Java
๋ณต์‚ฌ

์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

Principal vs Authentication vs User

์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฃผ์š” ์ธํ„ฐํŽ˜์ด์Šค ๋ฐ ํด๋ž˜์Šค
๋‹ค์Œ์€ Spring Security์˜ ์ฃผ์š” ์ธํ„ฐํŽ˜์ด์Šค์™€ ํด๋ž˜์Šค์˜ ๊ตฌ์กฐ๋„์ž…๋‹ˆ๋‹ค
java.security.Principal (Interface) โ†‘ Authentication (Interface) โ†‘ UsernamePasswordAuthenticationToken (๊ตฌํ˜„์ฒด) โ†‘ getPrincipal() โ†ณ org.springframework.security.core.userdetails.User (implements UserDetails)
Java
๋ณต์‚ฌ
์ด ๊ตฌ์กฐ๋„๋Š” Spring Security์—์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ณ„์ธตํ™”๋˜์–ด ์žˆ๋Š”์ง€๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
Principal์€ ์ตœ์ƒ์œ„ ์ธํ„ฐํŽ˜์ด์Šค์ด๋ฉฐ, Authentication์€ ์ด๋ฅผ ํ™•์žฅํ•˜์—ฌ ๋” ๋งŽ์€ ์ธ์ฆ ๊ด€๋ จ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. UsernamePasswordAuthenticationToken์€ Authentication์˜ ๊ตฌํ˜„์ฒด ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, getPrincipal() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด UserDetails๋ฅผ ๊ตฌํ˜„ํ•œ User ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
์ด์ œ, ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  CustomUser ๊ฐ์ฒด๋กœ ์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
java.security.Principal (Interface) โ†‘ Authentication (Interface) โ†‘ UsernamePasswordAuthenticationToken (๊ตฌํ˜„์ฒด) โ†‘ getPrincipal() โ†ณ org.springframework.security.core.userdetails.User โ†ณ com.aloha.security6.domain.CustomUser (implements UserDetails) : Users (์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ์ฒด)
Java
๋ณต์‚ฌ
์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ์ž ์ •์˜๋กœ ์‚ฌ์šฉํ•  ๊ฐ์ฒด๋ฅผ CustomUser ๋กœ, UserDetails ์„ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
public String home(@AuthenticationPrincipal CustomUser authUser, Model model) throws Exception { if( authUser != null ) { log.info("authUser : " + authUser); Users user = authUser.getUser(); model.addAttribute("user", user); } return "index"; }
Java
๋ณต์‚ฌ
์ด์ œ ์„ธ์…˜์— ๋“ฑ๋ก๋œ CustomUser ๊ฐ์ฒด์—์„œ Users ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์™€ ๋“ฑ๋ก๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ”๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํด๋ž˜์Šค/์ธํ„ฐํŽ˜์ด์Šค
์—ญํ• 
์ฃผ์š” ๋ฉ”์„œ๋“œ
์ฃผ์š” ๊ตฌํ˜„์ฒด
Principal
์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ๊ธฐ๋ณธ ์ •๋ณด ์ œ๊ณต (ํ‘œ์ค€ ์ž๋ฐ” ์ธํ„ฐํŽ˜์ด์Šค)
getName()
Spring Security์˜ User, ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๊ฐ์ฒด
Authentication
Spring Security ์ธ์ฆ ์ •๋ณด (์‚ฌ์šฉ์ž, ๊ถŒํ•œ, ์ธ์ฆ ์ƒํƒœ ๋“ฑ ํฌํ•จ)
getPrincipal(), getAuthorities(), isAuthenticated()
UsernamePasswordAuthenticationToken, JwtAuthenticationToken
User
์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ์„ธ๋ถ€ ์ •๋ณด ์ œ๊ณต (Spring Security์˜ ๊ตฌํ˜„์ฒด, UserDetails๋ฅผ ๊ตฌํ˜„)
getUsername(), getAuthorities(), isAccountNonLocked() ๋“ฑ
Spring Security ๊ธฐ๋ณธ User ํด๋ž˜์Šค
โ€ข
Principal: ์ตœ์†Œํ•œ์˜ ์‚ฌ์šฉ์ž ์‹๋ณ„ ์ •๋ณด๋ฅผ ์ œ๊ณต.
โ€ข
Authentication: ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ํฌํ•จ.
โ€ข
User: Spring Security์—์„œ ์‚ฌ์šฉ์ž ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„์ฒด.