์ฌ์ฉ์ ์ ์ ์ธ์ฆ
์ด์ ํ์ด์ง
์ด์ ํ์ด์ง ๋ด์ฉ์ ์ด์ด์ ์งํํฉ๋๋ค.
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-thymeleaf'
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=form-custom
# ๋ฐ์ดํฐ ์์ค - 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.security6.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 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
๋ณต์ฌ
์คํ๋ง ์ํ๋ฆฌํฐ ์ค์
โข
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
public class SecurityConfig {
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailServiceImpl userDetailServiceImpl;
// ์คํ๋ง ์ํ๋ฆฌํฐ ์ค์ ๋ฉ์๋
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// โ
์ธ๊ฐ ์ค์
http.authorizeRequests(requests -> requests
.antMatchers("/**").permitAll()
.anyRequest().permitAll()
);
// ๐ ํผ ๋ก๊ทธ์ธ ์ค์
// โ
์ปค์คํ
๋ก๊ทธ์ธ ํ์ด์ง
http.formLogin(login -> login.permitAll() );
// โ
์ฌ์ฉ์ ์ ์ ์ธ์ฆ ์ค์
http.userDetailsService(userDetailServiceImpl);
// ๐ ์๋ ๋ก๊ทธ์ธ ์ค์
http.rememberMe(me -> me.key("aloha")
.tokenRepository(tokenRepository())
.tokenValiditySeconds(60 * 60 * 24 * 7));
return http.build();
}
/**
* ๐ ๋น๋ฐ๋ฒํธ ์ํธํ ๋น ๋ฑ๋ก
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* ๐ AuthenticationManager ์ธ์ฆ ๊ด๋ฆฌ์ ๋น ๋ฑ๋ก
* @param authenticationConfiguration
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration ) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* ๐ฎโโ๏ธ๐ ์ฌ์ฉ์ ์ธ์ฆ ๊ด๋ฆฌ ๋น ๋ฑ๋ก ๋ฉ์๋
* JDBC ์ธ์ฆ ๋ฐฉ์
* โ
๋ฐ์ดํฐ ์์ค (URL, ID, PW) - application.properties
* โ
SQL ์ฟผ๋ฆฌ ๋ฑ๋ก
* โญ ์ฌ์ฉ์ ์ธ์ฆ ์ฟผ๋ฆฌ
* โญ ์ฌ์ฉ์ ๊ถํ ์ฟผ๋ฆฌ
* @return
*/
// @Bean
// public UserDetailsService userDetailsService() {
// JdbcUserDetailsManager userDetailsManager
// = new JdbcUserDetailsManager(dataSource);
// // ์ฌ์ฉ์ ์ธ์ฆ ์ฟผ๋ฆฌ
// String sql1 = " SELECT username, password, enabled "
// + " FROM user "
// + " WHERE username = ? "
// ;
// // ์ฌ์ฉ์ ๊ถํ ์ฟผ๋ฆฌ
// String sql2 = " SELECT username, auth "
// + " FROM user_auth "
// + " WHERE username = ? "
// ;
// userDetailsManager.setUsersByUsernameQuery(sql1);
// userDetailsManager.setAuthoritiesByUsernameQuery(sql2);
// return userDetailsManager;
// }
/**
* ๐ ์๋ ๋ก๊ทธ์ธ ์ ์ฅ์ ๋น ๋ฑ๋ก
* โ
๋ฐ์ดํฐ ์์ค
* โญ persistent_logins ํ
์ด๋ธ ์์ฑ
create table persistent_logins (
username varchar(64) not null
, series varchar(64) primary key
, token varchar(64) not null
, last_used timestamp not null
);
* ๐ ์๋ ๋ก๊ทธ์ธ ํ๋ก์ธ์ค
* โ
๋ก๊ทธ์ธ ์
* โก ๐ฉโ๐ผ(ID, ์๋ฆฌ์ฆ, ํ ํฐ) ์ ์ฅ
* โ
๋ก๊ทธ์์ ์,
* โก ๐ฉโ๐ผ(ID, ์๋ฆฌ์ฆ, ํ ํฐ) ์ญ์
* @return
*/
@Bean
public PersistentTokenRepository tokenRepository() {
// JdbcTokenRepositoryImpl : ํ ํฐ ์ ์ฅ ๋ฐ์ดํฐ ๋ฒ ์ด์ค๋ฅผ ๋ฑ๋กํ๋ ๊ฐ์ฒด
JdbcTokenRepositoryImpl repositoryImpl = new JdbcTokenRepositoryImpl();
// โ
ํ ํฐ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ ์์ค ์ง์
// - ์ํ๋ฆฌํฐ๊ฐ ์๋ ๋ก๊ทธ์ธ ํ๋ก์ธ์ค๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ DB๋ฅผ ์ง์ ํฉ๋๋ค.
repositoryImpl.setDataSource(dataSource);
// persistent_logins ํ
์ด๋ธ ์์ฑ
try {
repositoryImpl.getJdbcTemplate().execute(JdbcTokenRepositoryImpl.CREATE_TABLE_SQL);
}
catch (BadSqlGrammarException e) {
log.error("persistent_logins ํ
์ด๋ธ์ด ์ด๋ฏธ ์กด์ฌํฉ๋๋ค.");
}
catch (Exception e) {
log.error("์๋ ๋ก๊ทธ์ธ ํ
์ด๋ธ ์์ฑ ์ค , ์์ธ ๋ฐ์");
}
return repositoryImpl;
}
}
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์์ ์ฌ์ฉ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ์ฒด.