RequestInfo
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RequestInfo {
private String remoteIp;
private String sessionId;
private LocalDateTime loginTime;
}
CustomAuthDetail
@Component
public class CustomAuthDetails implements AuthenticationDetailsSource<HttpServletRequest, RequestInfo> {
// 로그인이 일어날때 request에서 정보를 가져다가 넣어서 보내주는 것
@Override
public RequestInfo buildDetails(HttpServletRequest request) {
return RequestInfo.builder()
.remoteIp(request.getRemoteAddr())
.sessionId(request.getSession().getId())
.loginTime(LocalDateTime.now())
.build();
}
}
SecurityConfig
@EnableWebSecurity(debug = true)
// 유저로 로그인 시 관리자 페이지까지 접근 되는 것을 막기 위해 밑 어노테이션 지정
// 지정 시 Controller에 지정한 @PreAuthorize가 반영됨
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAuthDetails customAuthDetails;
public SecurityConfig(CustomAuthDetails customAuthDetails) {
this.customAuthDetails = customAuthDetails;
}
// 관리자가 User page 접근 시 에러가 안나고 접근할 수 있게 하는 코드
@Bean
RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
return roleHierarchy;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(request->{
request
.antMatchers("/").permitAll()
// 메인 페이지 말고는 허락 받고 들어와
// 이것만 설정시 CSS가 내려 가지 않음
.anyRequest().authenticated();
})
.formLogin(
login -> login.loginPage("/login")
// 현재 위에서 (/), 즉 메인페이지말고는 로그인을 받고 들어갈 수 있음(로그인 페이지 또한 인증이 필요한것으로 설정)
.permitAll() // 따라서 permitAll() 붙이지 않으면 무한 루프 발생할수 있음
// true로 하면 특정 사이트에 접속하기위해 로그인 했지만 다시 메인페이지로 돌아감
.defaultSuccessUrl("/", false)
.failureUrl("/login-error")
.authenticationDetailsSource(customAuthDetails)
)
// 로그 아웃시 메인 페이지로 가도록 하기
.logout( logout->logout.logoutSuccessUrl("/"))
// 유저가 관리자 페이지에 접근할때 뜬 에러(403)시 띄울 페이지 연결
.exceptionHandling(exception -> exception.accessDeniedPage("/access-denied"))
;
}
@Override
public void configure(WebSecurity web) throws Exception {
// 웹 리소스에 대해서는 시큐리티 필터가 작동하지 않도록 ignore
web.ignoring()
.requestMatchers(
PathRequest.toStaticResources().atCommonLocations()
);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(
// 테스트에 한정에서 사용하는 메서드
User.withDefaultPasswordEncoder()
.username("user1")
.password("1111")
.roles("USER")
).withUser(
User.withDefaultPasswordEncoder()
.username("admin")
.password("2222")
.roles("ADMIN")
);
}
}
Home Controller
@Controller
public class HomeController {
@GetMapping("/")
public String main(){
return "index";
}
@GetMapping("/login")
public String login(){
return "loginForm";
}
@GetMapping("/login-error")
public String loginError(Model model){
model.addAttribute("loginError", true);
return "loginForm";
}
// CustomAuthDetails 확인
// 각 build한 변수에 값이 들어간것을 확인할 수 있다
@GetMapping("/auth")
@ResponseBody // 이를 설정하여야 json 형태로 body가 내려감
public Authentication auth(){
return SecurityContextHolder.getContext().getAuthentication();
}
@GetMapping("/access-denied")
public String accessDenied(){
return "AccessDenied";
}
@PreAuthorize("hasAnyAuthority('ROLE_USER')")
@GetMapping("/user-page")
public String userPage(){
return "UserPage";
}
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
@GetMapping("/admin-page")
public String adminPage(){
return "AdminPage";
}
}
thymeleaf 에서 security를 적용하는 태그
ex) ROLE_USER 시에만 보이게 되는 화면 구현
<div sec:authorize="isAuthenticated()">
This content is only shown to authenticated users.
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">
This content is only shown to administrators.
</div>
<div sec:authorize="hasRole('ROLE_USER')">
This content is only shown to users.
</div>