Contents
Session을 활용한 Spring Security 인증Session을 활용한 Spring Security 인증
- Authentication 내부에 유저 정보 및 권한 정보를 담음 (복제되지 않음 : Singleton)
- 인증이 성공할 경우 Authentication에 접근할 수 있는 키를 사용자에게 제공
Codes
- SecurityConfig.java
- sameOrigin 옵션 : 권한 설정으로 인해 h2-console 접근 거부 해결
- CSRF Filter는 해당 사용자의 주소가 허용되어야 하기 때문에 예제에서는 OFF
- 권한이 없는 유저가 다른 주소로 접근하려고 하면 자동으로 로그인 페이지로 이동 (세션 체크)
- 로그인 성공 시 main으로 redirect
- main은 인증이 필요
/user/**
는 USER 권한이 있어야만 접속 가능/admin/**
는 ADMIN 권한이 있어야만 접속 가능
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()));
http.csrf(configure -> configure.disable());
http.formLogin(form -> form
.loginPage("/login-form")
.loginProcessingUrl("/login") // username=ssar&password=1234
.defaultSuccessUrl("/main"));
http.authorizeHttpRequests(
authorize -> authorize
.requestMatchers("/main").authenticated()
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().permitAll()
);
return http.build();
}
}
- User.java
- UserDetails 인터페이스를 상속
- User는 이제 User이자 UserDetails → Spring Security에서 사용하는 UserDetails 정보를 대신할 수 있음
- id, username, password, email 외에 roles 추가
- 유저의 권한 지정
- 해당 예제에서는 유저에게 여러 권한을 주는 방식 채택
- (USER, ADMIN) 또는 (USER)
- getAuthorities : 로그인 시 Session에 User를 저장할 때 권한도 같이 불러와서 저장
@NoArgsConstructor
@Getter
@Entity
@Table(name = "user_tb")
public class User implements UserDetails {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
private String username;
private String password;
private String email;
private String roles;
@Builder
public User(Integer id, String username, String password, String email, String roles) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.roles = roles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
String[] roleList = roles.split(",");
for (String role : roleList) {
authorities.add(() -> "ROLE_" + role);
}
return authorities;
}
}
- UserController.java
- /user : USER 권한 확인 용 health checker
- /main
- @AuthenticationPrincipal 어노테이션이 붙어있을 경우 해당 유저의 정보를 인증 정보로 사용
- Authentication에 저장
@Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/user")
public @ResponseBody String user() {
return "<h1>user page</h1>";
}
@GetMapping("/main")
public String main(@AuthenticationPrincipal User user) {
System.out.println(user.getUsername());
return "main";
}
@GetMapping("/join-form")
public String joinForm() {
return "user/join-form";
}
@GetMapping("/login-form")
public String loginForm() {
return "user/login-form";
}
@PostMapping("/join")
public String join(String username, String password, String email) {
userService.회원가입(username, password, email);
return "redirect:/main";
}
}
- AdminController.java
- /admin : ADMIN 권한 확인 용 health checker
@Controller
public class AdminController {
@GetMapping("/admin")
public @ResponseBody String adminMain() {
return "<h1>admin page</h1>";
}
}
- UserService.java
- UserDetailsService 인터페이스를 상속 : UserDetailsService 메서드도 함께 사용 가능
- 회원가입 : 비밀번호가 입력되면 BCrypt에 의해 비밀번호가 해시로 암호화되어 저장
- loadUserByUsername : 로그인 시 해당 username을 사용한 User 객체 검색 메서드
- 메서드 재정의
@Service
public class UserService implements UserDetailsService {
private UserRepository userRepository;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public UserService(UserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userRepository = userRepository;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Transactional
public void 회원가입(String username, String password, String email) {
String encPassword = bCryptPasswordEncoder.encode(password);
String roles = "USER";
userRepository.save(username, encPassword, email, roles);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
- UserRepository.java
- em → userRepository → userService → userController 순의 IoC 구조
- 저장, 검색 등 DB와 직접 통신하는 메서드만 작성
@Repository
public class UserRepository {
private EntityManager em;
public UserRepository(EntityManager em) {
this.em = em;
}
public void save(String username, String password, String email, String roles) {
em.createNativeQuery("insert into user_tb (username, password, email, roles) values (?, ?, ?, ?)")
.setParameter(1, username)
.setParameter(2, password)
.setParameter(3, email)
.setParameter(4, roles)
.executeUpdate();
}
public User findByUsername(String username) {
try {
Query query = em.createNativeQuery("select * from user_tb where username = ?", User.class);
query.setParameter(1, username);
return (User) query.getSingleResult();
} catch (Exception e) { // 못찾으면 예외가 발생
return null;
}
}
}
- data.sql
- 비밀번호를 암호화하여 저장했기 때문에 DB 내에도 암호화해서 저장해둔 더미가 필요
- 비밀번호 검증은 로그인 시 자동으로 AuthenticationProvider가 수행
- ssar는 USER, cos는 USER, ADMIN 권한을 다 가지고 있음
insert into user_tb(username, password, email, roles) values('ssar', '$2a$10$sZvrnV2FZO51MGrG2jTUU.zv3/K/vZFBW5MOYWPTkVeDeoZhH3rai', 'ssar@nate.com', 'USER');
insert into user_tb(username, password, email, roles) values('cos', '$2a$10$sZvrnV2FZO51MGrG2jTUU.zv3/K/vZFBW5MOYWPTkVeDeoZhH3rai', 'ssar@nate.com', 'ADMIN,USER');
Results
- ssar 로그인 시
- /main 또는 기타 URL로 접근 시도 시, 비 로그인 상태면 로그인 페이지로 자동 이동


- 로그인 성공 시 Console에 로그인한 유저 확인 가능

- ssar은 USER 권한만 있으므로 /user는 접속되고 /admin은 권한 없음으로 접속 불가 (403)


- cos 로그인 시
- cos는 USER, ADMIN 권한이 다 존재하므로 둘 다 접속 가능



Share article