私は完全にJSON通信を使用するspring/spring-mvcのアプリケーションを持っています。ここで、JSONを介してSpring Security 3(LdapAuthenticationProviderを使用)でアプリケーションを認証する必要があります。
デフォルトの春の安全性送信フォームには、次のようなPOSTが必要です。
POST /myapp/j_spring_security_check HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
Host: 127.0.0.1:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (Java 1.5)
j_username=myUsername&j_password=myPass
しかし、私はこのようなJSONオブジェクトを渡したいです:
{"j_username":"myUsername","j_password":"myPass"}
this 、 this other 、または this one のような多くの投稿を読みました。すべてのajaxの場合はPOST上記のように。
何か案は?
JSONを解析する独自のセキュリティフィルターを作成できます。
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/core-web-filters.html
BasicAuthenticationFilterを参照として使用できます。
ケビンの提案によると、
そしてこの投稿を読んだ後: 1 、 2 、ドキュメント、そして this に感謝ブログ投稿、
認証前にJSONを直接管理する独自のFORM_LOGIN_FILTERを作成しました。
コミュニティにコードを貼り付けます。
目標は、古典的なブラウザ形式の両方を許可することですPOST JSONベースの認証を使用した認証。JSON認証でもloginSuccesful.htmへのリダイレクトを回避したい
コンテキスト内:
<security:http use-expressions="true" auto-config="false" entry-point-ref="http403EntryPoint">
<security:intercept-url pattern="/logs/**" access="denyAll" />
<!-- ... All other intercept URL -->
<security:custom-filter ref="CustomUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER "/>
<security:logout
invalidate-session="true"
logout-success-url="/LogoutSuccessful.htm"
delete-cookies="true"
/>
<security:session-management>
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</security:session-management>
<security:access-denied-handler error-page="/accessDenied.htm" />
</security:http>
<bean id="CustomUsernamePasswordAuthenticationFilter" class="path.to.CustomUsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="customSuccessHandler"/>
<property name="authenticationFailureHandler" ref="failureHandler"/>
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
<property name="usernameParameter" value="j_username"/>
<property name="passwordParameter" value="j_password"/>
</bean>
<bean id="customSuccessHandler" class="path.to.CustomAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/login.htm" />
<property name="targetUrlParameter" value="/LoginSuccessful.htm" />
</bean>
<bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/login.htm" />
</bean>
<bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
CustomUsernamePasswordAuthenticationFilterクラス:
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private String jsonUsername;
private String jsonPassword;
@Override
protected String obtainPassword(HttpServletRequest request) {
String password = null;
if ("application/json".equals(request.getHeader("Content-Type"))) {
password = this.jsonPassword;
}else{
password = super.obtainPassword(request);
}
return password;
}
@Override
protected String obtainUsername(HttpServletRequest request){
String username = null;
if ("application/json".equals(request.getHeader("Content-Type"))) {
username = this.jsonUsername;
}else{
username = super.obtainUsername(request);
}
return username;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
if ("application/json".equals(request.getHeader("Content-Type"))) {
try {
/*
* HttpServletRequest can be read only once
*/
StringBuffer sb = new StringBuffer();
String line = null;
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null){
sb.append(line);
}
//json transformation
ObjectMapper mapper = new ObjectMapper();
LoginRequest loginRequest = mapper.readValue(sb.toString(), LoginRequest.class);
this.jsonUsername = loginRequest.getUsername();
this.jsonPassword = loginRequest.getPassword();
} catch (Exception e) {
e.printStackTrace();
}
}
return super.attemptAuthentication(request, response);
}
}
CustomAuthenticationSuccessHandlerクラス:
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication auth
)throws IOException, ServletException {
if ("application/json".equals(request.getHeader("Content-Type"))) {
/*
* USED if you want to AVOID redirect to LoginSuccessful.htm in JSON authentication
*/
response.getWriter().print("{\"responseCode\":\"SUCCESS\"}");
response.getWriter().flush();
} else {
super.onAuthenticationSuccess(request, response, auth);
}
}
}
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
LoginRequest loginRequest = this.getLoginRequest(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private LoginRequest getLoginRequest(HttpServletRequest request) {
BufferedReader reader = null;
LoginRequest loginRequest = null;
try {
reader = request.getReader();
Gson gson = new Gson();
loginRequest = gson.fromJson(reader, LoginRequest.class);
} catch (IOException ex) {
Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
reader.close();
} catch (IOException ex) {
Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
}
}
if (loginRequest == null) {
loginRequest = new LoginRequest();
}
return loginRequest;
}
}
ログインリクエストに別のリクエストボディパーサーが必要な場合は、UsernamePasswordAuthenticationFilter
を拡張し、attemptAuthentication
メソッドをオーバーライドします。デフォルトでは、UsernamePasswordAuthenticationFilter
はURLエンコードされたデータを解析し、そこからUsernamePasswordAuthenticationToken
を作成します。ここで、アプリケーションに送信するものをすべて解析するパーサーを作成するだけです。
{"username": "someusername", "password": "somepassword"}
を解析する例を次に示します
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
BufferedReader reader = request.getReader();
StringBuffer sb = new StringBuffer();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String parsedReq = sb.toString();
if (parsedReq != null) {
ObjectMapper mapper = new ObjectMapper();
AuthReq authReq = mapper.readValue(parsedReq, AuthReq.class);
return new UsernamePasswordAuthenticationToken(authReq.getUsername(), authReq.getPassword());
}
} catch (Exception e) {
System.out.println(e.getMessage());
throw new InternalAuthenticationServiceException("Failed to parse authentication request body");
}
return null;
}
@Data
public static class AuthReq {
String username;
String password;
}
}
スニペットリクエストの本文は文字列に抽出され、オブジェクトAuthReq
にマッピングされます(@Data
アノテーションはロンボクlibからのもので、セッターとゲッターを生成します)。デフォルトのUsernamePasswordAuthenticationToken
に渡されるAuthenticationProvider
を作成できるよりも。
これで、WebSecurityConfigurerAdapter
を拡張し、cnofigureメソッドをオーバーライドして古いフィルターを置き換えることができます。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login", "/logout").permitAll()
.anyRequest().authenticated()
.and().addFilterAt(new CustomUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin().loginProcessingUrl("/login")
.and()
.csrf().disable();
}
addFilterAt
メソッドを使用して、デフォルトのUsernamePasswordAuthenticationFilter
を置き換えます。 @EnableWebSecurity
アノテーションを使用することを忘れないでください。
this postによる別の方法は、コントローラーで直接スプリングセキュリティ認証を手動で管理することです。
この方法では、JSON入力を管理し、ログインリダイレクトを回避するのが非常に簡単です。
@Autowired
AuthenticationManager authenticationManager;
@ResponseBody
@RequestMapping(value="/login.json", method = RequestMethod.POST)
public JsonResponse mosLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
JsonResponse response = null;
try {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
token.setDetails(new WebAuthenticationDetails(request));
Authentication auth = authenticationManager.authenticate(token);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(auth);
if(auth.isAuthenticated()){
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
LoginResponse loginResponse = new LoginResponse();
loginResponse.setResponseCode(ResponseCodeType.SUCCESS);
response = loginResponse;
}else{
SecurityContextHolder.getContext().setAuthentication(null);
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setResponseCode(ResponseCodeType.ERROR);
response = errorResponse;
}
} catch (Exception e) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setResponseCode(ResponseCodeType.ERROR);
response = errorResponse;
}
return response;
}
Spring BootアプリケーションでJSON資格情報を使用してログインする場合、 fl4l および oe.elvik からの回答を適用しました。注釈ベースのBean構成で作業しています。
参照された回答では、認証マネージャーが挿入されるカスタムフィルターが作成されます。これを行うには、認証マネージャーがSpring Beanとして存在する必要があります。これを行う方法のリンクは次のとおりです。 https://stackoverflow.com/a/21639553/3950535 。
上記のソリューションのJava構成:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
@Bean
public AuthenticationFilter authenticationFilter() throws Exception{
AuthenticationFilter authenticationFilter = new AuthenticationFilter();
authenticationFilter.setUsernameParameter("username");
authenticationFilter.setPasswordParameter("password");
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setFilterProcessesUrl("/login");
authenticationFilter.setAuthenticationSuccessHandler(successHandler());
return authenticationFilter;
}
@Bean
public SuccessHandler successHandler(){
return new SuccessHandler();
}
この例を見てください: https://github.com/fuhaiwei/springboot_security_restful_api
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private CustomLoginHandler customLoginHandler;
@Autowired
private CustomLogoutHandler customLogoutHandler;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/basic/**").hasRole("BASIC")
.antMatchers("/api/session").permitAll()
.antMatchers(HttpMethod.GET).permitAll()
.antMatchers("/api/**").hasRole("BASIC");
http.formLogin();
http.logout()
.logoutUrl("/api/session/logout")
.addLogoutHandler(customLogoutHandler)
.logoutSuccessHandler(customLogoutHandler);
http.exceptionHandling()
.accessDeniedHandler(customAccessDeniedHandler)
.authenticationEntryPoint(customAccessDeniedHandler);
http.csrf()
.ignoringAntMatchers("/api/session/**");
http.addFilterBefore(new AcceptHeaderLocaleFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
private CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(customLoginHandler);
filter.setAuthenticationFailureHandler(customLoginHandler);
filter.setAuthenticationManager(authenticationManager());
filter.setFilterProcessesUrl("/api/session/login");
return filter;
}
private static void responseText(HttpServletResponse response, String content) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
response.setContentLength(bytes.length);
response.getOutputStream().write(bytes);
response.flushBuffer();
}
@Component
public static class CustomAccessDeniedHandler extends BaseController implements AuthenticationEntryPoint, AccessDeniedHandler {
// NoLogged Access Denied
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
responseText(response, errorMessage(authException.getMessage()));
}
// Logged Access Denied
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
responseText(response, errorMessage(accessDeniedException.getMessage()));
}
}
@Component
public static class CustomLoginHandler extends BaseController implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
// Login Success
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
LOGGER.info("User login successfully, name={}", authentication.getName());
responseText(response, objectResult(SessionController.getJSON(authentication)));
}
// Login Failure
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
responseText(response, errorMessage(exception.getMessage()));
}
}
@Component
public static class CustomLogoutHandler extends BaseController implements LogoutHandler, LogoutSuccessHandler {
// Before Logout
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
}
// After Logout
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
responseText(response, objectResult(SessionController.getJSON(null)));
}
}
private static class AcceptHeaderLocaleFilter implements Filter {
private AcceptHeaderLocaleResolver localeResolver;
private AcceptHeaderLocaleFilter() {
localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Locale locale = localeResolver.resolveLocale((HttpServletRequest) request);
LocaleContextHolder.setLocale(locale);
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
}
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest;
try (InputStream is = request.getInputStream()) {
DocumentContext context = JsonPath.parse(is);
String username = context.read("$.username", String.class);
String password = context.read("$.password", String.class);
authRequest = new UsernamePasswordAuthenticationToken(username, password);
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken("", "");
}
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}