
@Controller
public class MainController {
@GetMapping("/")
public String mainP() {
return "main";
}
}
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Main Page</title>
</head>
<body>
main page
</body>
</html>
hasRole(Role) : 해당 Role 을 갖고있는 사용자 허용hasAnyRole(Role1, Role2, ...) : 해당 Role 중에 1개이상 갖고있는 사용자 허용anonymous() : 익명 사용자 허용authenticated() : 인증된 사용자 허용permitAll() : 모든 사용자 허용denyAll() : 모든 사용자 거부@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
);
return http.build();
}
}
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Login Page</title>
</head>
<body>
login page
<hr>
<form action="/loginProc" method="post" name="loginForm">
<input id="username" type="text" name="username" placeholder="id"/>
<input id="password" type="password" name="password" placeholder="password"/>
<input type="submit" value="login"/>
</form>
</body>
</html>
@Controller
public class LoginController {
@GetMapping("/login")
public String loginP() {
return "login";
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
);
http
.formLogin((auth) -> auth
.loginPage("/login")
.loginProcessingUrl("/loginProc")
.permitAll()
);
http
.csrf((auth) -> auth
.disable()
);
return http.build();
}
}
BCryptPasswordEncoder를 제공하고 권장@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Join Page</title>
</head>
<body>
join page
<hr>
<form action="/joinProc" method="post" name="joinForm">
<input id="username" type="text" name="username" placeholder="Username"/>
<input id="password" type="password" name="password" placeholder="Password"/>
<input type="submit" value="Join"/>
</form>
</body>
</html>
@Getter
@Setter
public class JoinDto {
private String username;
private String password;
}
@Controller
@RequiredArgsConstructor
public class JoinController {
private final JoinService joinService;
@GetMapping("/join")
public String joinP() {
return "join";
}
@PostMapping("/joinProc")
public String joinProcess(JoinDto joinDto) {
joinService.joinProcess(joinDto);
return "redirect:/login";
}
}
@Service
@RequiredArgsConstructor
public class JoinService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public void joinProcess(JoinDto joinDto) {
//db에 동일한 username을 가진 회원이 존재하는지 검증
User user = new User();
user.setUsername(joinDto.getUsername());
user.setPassword(bCryptPasswordEncoder.encode(joinDto.getPassword()));
user.setRole("ROLE_USER");
userRepository.save(user);
}
}
@Entity
@Getter
@Setter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
}
public interface UserRepository extends JpaRepository<User, Long> {
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login", "/loginProc", "/join", "/joinProc").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
);
http
.formLogin((auth) -> auth
.loginPage("/login")
.loginProcessingUrl("/loginProc")
.permitAll()
);
http
.csrf((auth) -> auth
.disable()
);
return http.build();
}
}
@Entity
@Getter
@Setter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
private String role;
}
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByUsername(String username);
}
@Service
@RequiredArgsConstructor
public class JoinService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public void joinProcess(JoinDto joinDto) {
//db에 동일한 username을 가진 회원이 존재하는지 검증
boolean isUser = userRepository.existsByUsername(joinDto.getUsername());
if (isUser) {
return;
}
User user = new User();
user.setUsername(joinDto.getUsername());
user.setPassword(bCryptPasswordEncoder.encode(joinDto.getPassword()));
user.setRole("ROLE_USER");
userRepository.save(user);
}
}
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user != null) {
return new CustomUserDetails(user);
}
return null;
}
}
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByUsername(String username);
User findByUsername(String username);
}
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collection;
}
@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 true;
}
}
String id = SecurityContextHolder.getContext().getAuthentication().getName();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iter = authorities.iterator();
GrantedAuthority auth = iter.next();
String role = auth.getAuthority();
server:
session:
timeout: 1800server:
session:
timeout: 90m@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
.sessionManagement((auth) -> auth
.maximumSessions(1)
.maxSessionsPreventsLogin(true));
return http.build();
}
sessionManagement() 메소드를 통해 설정 진행maximumSessions(정수): 하나의 아이디에 대한 다중 로그인 허용 개수maxSessionspreventsLogin(boolean): 다중 로그인 개수를 초과 하였을 때 처리 방법true: 초과시 새로운 로그인 차단false: 초과시 기존 세션 하나 삭제
sessionManagement()메소드의 sessionFixation()메소드를 통해서 세션 고정 공격 보호 설정 가능sessionManagement().sessionFixation().none(): 로그인 시 세션 정보 변경XsessionManagement().sessionFixation().newSession(): 로그인 시 새로운 세션 생성sessionManagement().sessionFixation().changeSessionId: 로그인 시 동일한 세션에 대한 id 변경@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
.sessionManagement((auth) -> auth
.sessionFixation().changeSessionId());
return http.build();
}
csrf.disable()구문 삭제<form action="/loginProc" method="post" name="loginForm">
<input id="username" type="text" name="username" placeholder="id"/>
<input id="password" type="password" name="password" placeholder="password"/>
<input type="hidden" name="_csrf" value="{{_csrf.token}}"/>
<input type="submit" value="login"/>
</form>
<head> 구획에 아래 요소 추가<meta name="_csrf" content="{{_csrf.token}}"/>
<meta name="_csrf_header" content="{{_csrf.headerName}}"/>
GET 방식 로그아웃을 처리할 때 설정
csrf 설정시 POST 요청으로 로그아웃을 진행해야 하지만 아래 방식을 통해 GET 방식으로 진행할 수 있음
SecurityConfig 로그아웃 설정
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
.logout((auth) -> auth.logoutUrl("/logout")
.logoutSuccessUrl("/"));
return http.build();
}
LogoutController
@Controller
public class LogoutController {
@GetMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
new SecurityContextLogoutHandler().logout(request, response, authentication);
}
return "redirect:/";
}
}
spring:
mustache:
servlet:
expose-request-attributes: true
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user1 = User.builder()
.username("user1")
.password(bCryptPasswordEncoder().encode("1234"))
.roles("ADMIN")
.build();
UserDetails user2 = User.builder()
.username("user2")
.password(bCryptPasswordEncoder().encode("1234"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user1, user2);
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.httpBasic(Customizer.withDefaults());
return http.build();
}
fromHierarchy()메소드 사용@Bean
public RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.fromHierarchy("""
ROLE_C > ROLE_B
ROLE_B > ROLE_A
""");
}
@Bean
public RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.withRolePrefix("접두사_")
.role("C").implies("B")
.role("B").implies("A")
.build();
}
@Bean
public RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.withDefaultRolePrefix()
.role("C").implies("B")
.role("B").implies("A")
.build();
}
참조 링크
개발자 유미 - 스프링 시큐리티