사용자 정의 로그인 양식.JSON 응답을 가져오도록 Spring 보안 구성
나는 두 부분으로 나뉜 간단한 애플리케이션이 있습니다.
- Spring-boot / Spring-security로 REST 서비스를 제공하는 백엔드
- 정적 파일만 포함하는 프런트 엔드입니다.
요청은 포트 80에서 수신하는 anginx 서버에 의해 수신됩니다.
- 요청 URL이 /api/로 시작하면 요청이 백엔드로 리디렉션됩니다.
- 그렇지 않으면 정적 파일을 제공하는 nginx에서 요청을 처리합니다.
사용자 정의 로그인 양식(프론트엔드 부분)을 생성하여 Spring-boot 서버를 구성하려고 합니다.
"로그인 성공" URL과 "로그인 오류" URL을 정의하는 방법을 볼 수 있는 많은 예가 있지만 Spring-security에서 사용자를 리디렉션하는 것을 원하지 않습니다.로그인에 성공했거나 HTTP 40x가 로그인에 실패한 경우 Spring-security에서 HTTP 200으로 응답하기를 원합니다.
즉, 저는 백엔드가 HTML이 아닌 JSON으로만 응답하기를 원합니다.
지금까지는 로그인 양식을 제출하면 요청이 리디렉션되고 기본 Spring 로그인 양식을 답변으로 받습니다.
사용하려고 했습니다..formLogin().loginProcessingUrl("/login");
대신에loginPage("")
:
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("ADMIN");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login");
M 덕분에.Deinum과 이 가이드 덕분에 해결책을 찾을 수 있었습니다.
먼저 로그인 양식 자체에 구성 문제가 있었습니다.백엔드의 컨텍스트 경로는 다음과 같이 설정됩니다./api
사용자 정의 양식은 양식 매개변수를 제출해야 합니다./api/login
하지만 사실은 제가 자료를 제출하고 있었어요./api/login/
(추가 사항에 주의)/
마지막에).
결과적으로, 저는 자신도 모르게 보호된 리소스에 액세스하려고 했습니다!따라서 요청이 기본적으로 처리되었습니다.AuthenticationEntryPoint
기본 동작은 사용자를 로그인 페이지로 리디렉션하는 것입니다.
솔루션으로 사용자 지정 Authentication EntryPoint를 구현했습니다.
private AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.getWriter().append("Not authenticated");
httpServletResponse.setStatus(401);
}
};
}
그런 다음 구성에서 사용합니다.
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint())
그리고 저는 다른 핸들러들에게도 똑같이 했습니다.
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("ADMIN");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(successHandler())
.failureHandler(failureHandler())
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.csrf().csrfTokenRepository(csrfTokenRepository()).and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
;
}
private AuthenticationSuccessHandler successHandler() {
return new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.getWriter().append("OK");
httpServletResponse.setStatus(200);
}
};
}
private AuthenticationFailureHandler failureHandler() {
return new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.getWriter().append("Authentication failure");
httpServletResponse.setStatus(401);
}
};
}
private AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.getWriter().append("Access denied");
httpServletResponse.setStatus(403);
}
};
}
private AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.getWriter().append("Not authenticated");
httpServletResponse.setStatus(401);
}
};
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
다음은 Spring Boot 2.2.5에 대한 구성입니다.릴리스:
package com.may.config.security;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("user")).roles("USER").and()
.withUser("admin").password(passwordEncoder().encode("admin")).roles("USER", "ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestCache().disable() // do not preserve original request before redirecting to login page as we will return status code instead of redirect to login page (this is important to disable otherwise session will be created on every request (not containing sessionId/authToken) to non existing endpoint aka curl -i -X GET 'http://localhost:8080/unknown')
.authorizeRequests()
.antMatchers("/health", "/swagger-ui.html/**", "/swagger-resources/**", "/webjars/springfox-swagger-ui/**", "/v2/api-docs").permitAll()
.anyRequest().hasRole("USER").and()
.exceptionHandling()
.accessDeniedHandler((req, resp, ex) -> resp.setStatus(SC_FORBIDDEN)) // if someone tries to access protected resource but doesn't have enough permissions
.authenticationEntryPoint((req, resp, ex) -> resp.setStatus(SC_UNAUTHORIZED)).and() // if someone tries to access protected resource without being authenticated (LoginUrlAuthenticationEntryPoint used by default)
.formLogin()
.loginProcessingUrl("/login") // authentication url
.successHandler((req, resp, auth) -> resp.setStatus(SC_OK)) // success authentication
.failureHandler((req, resp, ex) -> resp.setStatus(SC_FORBIDDEN)).and() // bad credentials
.sessionManagement()
.invalidSessionStrategy((req, resp) -> resp.setStatus(SC_UNAUTHORIZED)).and() // if user provided expired session id
.logout()
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()); // return status code on logout
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
여기서 중요한 측면:
http.requestCache().disable()
그렇지 않으면 존재하지 않는 엔드포인트에 대한 모든 요청에 대해 새 세션이 생성됩니다(예: curl -i -X GET 'http://localhost:8080/unknown').
적어도 이것이 프로젝트에서 구성된 스프링-클램프에서 작동하는 방법입니다.
재정의되지 않은 경우 - ExceptionTranslationFilter는 AccessDenied를 처리하는 동안 requestCache를 사용하여 세션에 대한 원래 URL을 보존합니다(존재하지 않는 경우 세션을 만듭니다).예외.
http.sessionManagement().invalidSessionStrategy((req, resp) -> resp.setStatus(SC_UNAUTHORIZED))
사용자가 요청에 만료된 sessionId를 제공한 경우 401 상태 코드를 반환합니다.
재정의되지 않은 경우 - authenticationEntryPoint로 폴백합니다.
응답에 의미 있는 메시지를 제공하는 데 도움이 될 수 있습니다("세션이 만료되었습니다"라고도 함).
http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
로그아웃 시 200개 상태 코드 반환
재정의되지 않은 경우 - 웹 클라이언트를 로그인 페이지로 리디렉션합니다.
언급URL : https://stackoverflow.com/questions/32498868/custom-login-form-configure-spring-security-to-get-a-json-response
'programing' 카테고리의 다른 글
Android에서 앱 언어를 프로그래밍 방식으로 변경 (0) | 2023.07.10 |
---|---|
SQL Server 2005 쿼리를 CSV로 내보내는 방법 (0) | 2023.07.10 |
특정 커밋 이후 커밋을 어떻게 나열합니까? (0) | 2023.07.10 |
플라이웨이에서 마이그레이션을 스쿼시/병합하는 방법 (0) | 2023.07.10 |
Entity Framework로 작업할 때 좋은 설계 방법은 무엇입니까? (0) | 2023.07.10 |