Spring Boot Security
개발환경
- Spring Boot 2.1.x
- Gradle 4.10.2
build.gradle 수정
build.gradle
......
dependencies {
// ......
//compile('org.springframework.boot:spring-boot-starter-security')
//testCompile("org.springframework.security:spring-security-test")
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation "org.springframework.security:spring-security-test"
// ......
}
......
http://localhost:8080/ 에 접속하면 아이디와 비밀번호를 입력하라고 합니다.
디폴트 아이디는 user
이고, 디폴트 비밀번호는 로그에 아래와 같이 표시됩니다.
2020-03-21 15:47:05.526 INFO 14884 --- [ main] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: aa921732-e591-4632-b583-a1547910862c
2020-03-21 15:47:05.698 INFO 14884 --- [ main] o.s.s.web.DefaultSecurityFilterChain ......
커스텀 로그인
커스텀 아이디/패스워드 데이타베이스를 생성하고, 등록된 아이디로 로그인하도록 수정합니다.
테이블 생성
DROP TABLE `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`picture` varchar(255) DEFAULT NULL,
`role` varchar(255) NOT NULL,
`passwd` varchar(255) NULL,
`encpasswd` varchar(255) NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `user`(email,name, role, passwd)
VALUES('test@gmail.com', '테스트', 'USER', '{noop}aa921732-e591-4632-b583-a1547910862c');
user 엔터티 생성
src/main/java/kr/co/episode/example/domain/user/Role.java
@Getter
@RequiredArgsConstructor
public enum Role {
GUEST("ROLE_GUEST", "손님"),
USER("ROLE_USER", "일반 사용자"),
ADMIN("ROLE_ADMIN", "관리자");
private final String key;
private final String title;
}
src/main/java/kr/co/episode/example/domain/user/User.java
@Getter
@NoArgsConstructor
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String email;
@Column
private String picture;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
@Column
private String passwd;
@Builder
public User(String name, String email, String picture, Role role, String passwd) {
this.name = name;
this.email = email;
this.picture = picture;
this.role = role;
this.passwd = passwd;
}
public User update(String name, String picture) {
this.name = name;
this.picture = picture;
return this;
}
public String getRoleKey() {
return this.role.getKey();
}
}
src/main/java/kr/co/episode/example/domain/user/UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
user 서비스 생성
src/main/java/kr/co/episode/example/service/user/UserService.java
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<User> userWrapper = userRepository.findByEmail(email);
if(userWrapper.isEmpty()) {
throw new UsernameNotFoundException("Username not found");
}
User user = userWrapper.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if (("admin@example.com").equals(email)) {
authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getKey()));
} else {
authorities.add(new SimpleGrantedAuthority(Role.USER.getKey()));
}
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPasswd(), authorities);
}
}
src/main/java/kr/co/episode/example/config/SecurityConfig.java
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/", "/css/**", "/images/**", "/js/**", "/login", "/profile").permitAll()
.antMatchers("/api/v1/**", "/posts/save").hasRole(Role.USER.name())
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.formLogin();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
}
위 설정으로 데이타베이스에 입력한 계정정보를 이용해 로그인할 수 있습니다.
세션정보 이용하기
src/main/java/kr/co/episode/example/config/dto/SessionUserDto.java
@Getter
public class SessionUserDto implements Serializable {
private String name;
private String email;
private String picture;
public SessionUserDto(User user) {
this.name = user.getName();
this.email = user.getEmail();
this.picture = user.getPicture();
}
}
src/main/java/kr/co/episode/example/service/user/UserService.java
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final HttpSession httpSession;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
// ......
List<GrantedAuthority> authorities = new ArrayList<>();
httpSession.setAttribute("user", new SessionUserDto(user));
// ......
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPasswd(), authorities);
}
}
src/main/java/kr/co/episode/example/web/IndexController.java
@RequiredArgsConstructor
@Controller
public class IndexController {
// ......
private final HttpSession httpSession;
@GetMapping("/")
public String index(Model model, @PageableDefault Pageable pageable, @RequestParam Map<String, String> params) {
// ......
SessionUserDto sessionUserDto = (SessionUserDto) httpSession.getAttribute("user");
if (sessionUserDto != null) {
model.addAttribute("userName", sessionUserDto.getName());
}
model.addAttribute("posts", postsPagingService.findAllDesc(pageable, postsSearchDto));
model.addAttribute("search", postsSearchDto);
return "index";
}
// ......
}
src/main/resources/templates/index.html
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<a href="/posts/save" role="button" class="btn" btn-primary>글 등록</a>
<div th:if="${userName != null}" th:inline="text">
[[${userName}]] 님, 안녕하세요.
<a href="/logout" class="btn btn-info active" role="button">로그아웃</a>
</div>
<div th:if="${userName == null}">
<a href="/login" class="btn btn-success active" role="button">로그인</a>
</div>
</div>
</div>
</div>
세션정보 어노테이션으로 가져오기
src/main/java/kr/co/episode/example/config/LoginUser.java
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
src/main/java/kr/co/episode/example/config/LoginUserArgumentResolver.java
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
private final HttpSession httpSession;
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
boolean isUserClass = SessionUserDto.class.equals(parameter.getParameterType());
return isLoginUserAnnotation && isUserClass;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return httpSession.getAttribute("user");
}
}
src/main/java/kr/co/episode/example/config/WebConfig.java
@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginUserArgumentResolver loginUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserArgumentResolver);
}
}
src/main/java/kr/co/episode/example/web/IndexController.java
@RequiredArgsConstructor
@Controller
public class IndexController {
// ......
@GetMapping("/")
public String index(Model model, @PageableDefault Pageable pageable, @LoginUser SessionUserDto user, @RequestParam Map<String, String> params) {
// ......
if (user != null) {
model.addAttribute("userName", user.getName());
}
model.addAttribute("posts", postsPagingService.search(pageable, postsSearchDto));
model.addAttribute("search", postsSearchDto);
return "index";
}
// ......
}
Pingback: Spring Boot Oauth2 Client – 상구리의 기술 블로그