์ฌ์ฉ์ ์ ์ ์ธ์ฆ
์ด์ ํ์ด์ง
์ด์ ํ์ด์ง ๋ด์ฉ์ ์ด์ด์ ์งํํฉ๋๋ค.
Code
Preview
1.
๋ก๊ทธ์ธ ํ๋ฉด
2.
๋ฉ์ธ ํ๋ฉด
์์ ํ๋ก์ธ์ค
1.
ํ๋ก์ ํธ ์์ฑ
2.
ํ๋ก์ ํธ ์ค์
3.
๋๋ฉ์ธ
4.
์๋น์ค
5.
์คํ๋ง ์ํ๋ฆฌํฐ ์ค์
Preview
๋ก๊ทธ์ธ ํ๋ฉด
๋ฉ์ธ ํ๋ฉด
ํ๋ก์ ํธ ์์ฑ
build.gradle
spring boot 2.x.x
spring security 5.x.x
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '2.7.17'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.aloha'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '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:2.3.1'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
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:2.3.1'
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.form_custom.domain
mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
Markdown
๋ณต์ฌ
๋๋ฉ์ธ
โข
Users.java
โข
UserAuth.java
โข
CustomUser.java
Users.java
@Data
public class Users {
private String id;
private String username;
private String password;
private String name;
private String email;
private String profile;
private Date createdAt;
private Date updatedAt;
private int enabled;
private List<UserAuth> authList;
}
Java
๋ณต์ฌ
UserAuth.java
@Data
public class UserAuth {
private String id;
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.login(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.loginPage("/login")
.loginProcessingUrl("/login"));
// โ
์ฌ์ฉ์ ์ ์ ์ธ์ฆ ์ค์
http.userDetailsService(userDetailServiceImpl);
// ๐ ์๋ ๋ก๊ทธ์ธ ์ค์
http.rememberMe(me -> me.key("aloha")
.tokenRepository(tokenRepository())
.tokenValiditySeconds(60 * 60 * 24 * 7));
return http.build();
}
/**
* ๐ฎโโ๏ธ๐ ์ฌ์ฉ์ ์ธ์ฆ ๊ด๋ฆฌ ๋น ๋ฑ๋ก ๋ฉ์๋
* 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
๋ณต์ฌ