Hello, everyone, the National Day is over and Songko is back. Today we continue to send dry goods!

With regard to Spring Security, Songo has posted several posts about the use of the Security framework:

  1. Spring Security will take you hand in hand!
  2. Add a verification code for Spring Security login
  3. SpringSecurity logins use JSON data
  4. Role inheritance issues in Spring Security
  5. Using JWT! In Spring Security
  6. Spring Security works with OAuth2

In Spring Security, unauthenticated requests are redirected to the login page by default. However, in the case of a separate login, this default behavior is not appropriate. Today we’ll focus on how to implement an unauthenticated request that returns JSON directly instead of being redirected to the login page.

Front knowledge

I won’t go over the basic uses of Spring Security here, but if you don’t know, you can refer to the six articles above.

As you know, when customizing your Spring Security configuration, there are several properties:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .formLogin()
            .loginProcessingUrl("/doLogin")
            .loginPage("/login")
            // Other configurations
            .permitAll()
            .and()
            .csrf().disable();
}
Copy the code

There are two more important attributes:

  • LoginProcessingUrl: This specifies the address of the interface that processes the login request. For example, if you are logging in to a form, the action value in the form form will be the value specified here.
  • LoginPage: this represents the address of the loginPage, for example, when you access a resource that requires login, the system automatically redirects you to this page.

This configuration is fine in a logon that does not divide the front and back ends, but is problematic in a logon that separates the front and back ends. I take a simple example, for example, I want to visit/hello interface, but after this interface need to log in to access, I’m not login directly to access the interface, the system will give me back to 302, let me go to the login page, in the front and back end separation, my backend is generally not the login page, JSON is a reminder, For example:

@GetMapping("/login")
public RespBean login(a) {
    return RespBean.error("Not logged in, please log in!");
}
Copy the code

Complete code we can refer to my micro personnel project.

That is, when I access /hello directly without logging in, I see the JSON string above. In the case of back-end separation development, this looks fine (the back-end no longer does page jumps and returns JSON no matter what). But the problem is out of here, the system default jump is a redirect, that is to say when you visit/hello, the server will get back to the browser, 302, at the same time there are a Location field in the header, it has a value of http://localhost:8081/login, Also is to tell the browser you go visit http://localhost:8081/login address. Browser receives instruction, will go directly to http://localhost:8081/login address, if this time is the development environment and the request or an Ajax request, will occur across domains. Because front end separation development, the front end we usually start on NodeJS, then the front of all the requests made by NodeJS request, the request directly to the address of a service now tell the browser, the browser will directly go to http://localhost:8081/login, Instead of forwarding the request, you have a cross-domain problem.

The solution

Obviously, the above problem cannot be solved with a cross-domain approach, which may seem to solve the problem, but it is not the best solution.

If Spring Security were to request data that requires authentication when the user is not authenticated, instead of redirecting the user, it would simply return a JSON message telling the user that the request requires authentication.

This involves an interface in Spring Security, AuthenticationEntryPoint, which has an implementation class: Commence LoginUrlAuthenticationEntryPoint, the class has a method, as follows:

/** * Performs the redirect (or forward) to the login form URL. */
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
	String redirectUrl = null;
	if (useForward) {
		if (forceHttps && "http".equals(request.getScheme())) {
			redirectUrl = buildHttpsRedirectUrlForRequest(request);
		}
		if (redirectUrl == null) {
			String loginForm = determineUrlToUseForThisRequest(request, response,
					authException);
			if (logger.isDebugEnabled()) {
				logger.debug("Server side forward to: " + loginForm);
			}
			RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
			dispatcher.forward(request, response);
			return; }}else {
		redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
	}
	redirectStrategy.sendRedirect(request, response, redirectUrl);
}
Copy the code

First of all, we can see from the comment of this method that this method is used to determine whether to redirect or forward. By debugging trace, we can see that the default value of useForward is false, so the request is redirected.

To solve the problem, simply rewrite this method and return JSON instead of redirecting. The configuration is as follows:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .formLogin()
            .loginProcessingUrl("/doLogin")
            .loginPage("/login")
            // Other configurations
            .permitAll()
            .and()
            .csrf().disable().exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
                resp.setContentType("application/json; charset=utf-8");
                PrintWriter out = resp.getWriter();
                RespBean respBean = RespBean.error("Access failed!");
                if (authException instanceof InsufficientAuthenticationException) {
                    respBean.setMsg("Request failed, please contact administrator!");
                }
                out.write(newObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); }}); }Copy the code

Add the custom AuthenticationEntryPoint handler to your Spring Security configuration, which simply returns the corresponding JSON prompt. This way, if the user tries to access a request that requires authentication, no redirection will occur, and the server will simply send the browser a JSON message and the browser will do what it needs to do after receiving the JSON.

conclusion

Ok, a small redirection problem to share with friends, do not know if you understand? This is also a problem I recently encountered when refactoring micro personnel. It is expected that the Spring Boot version of micro personnel will be upgraded to the latest version in November, please pay attention to it.

Pay attention to the public account [Jiangnan little Rain], focus on Spring Boot+ micro service and front and back end separation and other full stack technology, regular video tutorial sharing, after attention to reply to Java, get Songko for you carefully prepared Java dry goods!