In the previous article, we discussed how to customize Spring Security login logic in a more elegant way to avoid the inefficiencies of custom filters. We recommend that you read this article and understand the authentication logic in Spring Security.

This article will continue to discuss how to store login user details on the basis of the above.

This is the 12th article in this series. Read the previous articles in this series to better understand this article:

  1. Dig a big hole and Spring Security will do it!
  2. How to decrypt the password
  3. A step-by-step guide to customizing form logins in Spring Security
  4. Spring Security does front and back separation, so don’t do page jumps! All JSON interactions
  5. Authorization in Spring Security used to be so simple
  6. How does Spring Security store user data into the database?
  7. Spring Security+Spring Data Jpa, Security management is only easier!
  8. Spring Boot + Spring Security enables automatic login
  9. Spring Boot automatic login. How to control security risks?
  10. How is Spring Security better than Shiro in microservices projects?
  11. Two ways for SpringSecurity to customize authentication logic (advanced play)

Well, without further ado, let’s look at today’s article.

1.Authentication

Authentication this interface and we have talked about many times, today also want to talk about again.

The Authentication interface is used to store our logged-in user information. In effect, it further encapsulates the Principal (java.security.Principal).

Let’s look at a definition of Authentication:

public interface Authentication extends Principal.Serializable {
	Collection<? extends GrantedAuthority> getAuthorities();
	Object getCredentials(a);
	Object getDetails(a);
	Object getPrincipal(a);
	boolean isAuthenticated(a);
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
Copy the code

The interface is explained as follows:

  1. The getAuthorities method is used to obtain the user’s permissions.
  2. The getCredentials method is used to obtain user credentials, generally passwords.
  3. The getDetails method is used to get the details the user carries, perhaps something like the current request.
  4. The getPrincipal method is used to get the current user, either a user name or a user object.
  5. IsAuthenticated whether the current user isAuthenticated successfully.

Here’s a more playful method called getDetails. The source code for this method is as follows:

Stores additional details about the authentication request. These might be an IP address, certificate serial number etc.

This method is used to store other information about authentication, such as IP addresses, certificate information, and so on.

In fact, by default, it stores the user’s login IP address and sessionId. Let’s look at it from a source code point of view.

2. Source code analysis

Scene SpringSecurity series has written article 12, read the previous articles, I believe you have understood the user login only one filter is UsernamePasswordAuthenticationFilter, AttemptAuthentication method of the attemptAuthentication method of this class, extracting the request parameters, and invoking a method in attemptAuthentication is setDetails.

Let’s look at the setDetails method:

protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
	authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
Copy the code

UsernamePasswordAuthenticationToken is a practical implementation of Authentication, so here is actually set in the details, as to the value of the details, Is built through authenticationDetailsSource, we’ll look at:

public class WebAuthenticationDetailsSource implements
		AuthenticationDetailsSource<HttpServletRequest.WebAuthenticationDetails> {
	public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
		return newWebAuthenticationDetails(context); }}public class WebAuthenticationDetails implements Serializable {
	private final String remoteAddress;
	private final String sessionId;
	public WebAuthenticationDetails(HttpServletRequest request) {
		this.remoteAddress = request.getRemoteAddr();

		HttpSession session = request.getSession(false);
		this.sessionId = (session ! =null)? session.getId() :null;
	}
    // omit other methods
}
Copy the code

The default build by WebAuthenticationDetailsSource WebAuthenticationDetails and set the result to the details of the Authentication properties. The properties defined in WebAuthenticationDetails, if you look at them, basically hold the user login address and sessionId.

So you basically get the idea here that the IP address for the user’s login can actually be retrieved directly from WebAuthenticationDetails.

Let me give you a simple example. For example, after we log in successfully, we can get the user IP anytime and anywhere in the following ways:

@Service
public class HelloService {
    public void hello(a) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails(); System.out.println(details); }}Copy the code

This fetch process is done in service to demonstrate the anytime, anywhere feature. We then call this method in controller, and when we access the interface, we see the following log:

WebAuthenticationDetails @ fffc7f0c: RemoteIpAddress: 127.0.0.1; SessionId: 303C7F254DF8B86667A2B20AA0667160Copy the code

As you can see, the user’s IP address and SessionId are given. Both of these properties have corresponding GET methods in WebAuthenticationDetails, and you can also get the property value separately.

3. The custom

Of course, WebAuthenticationDetails can also be customized, because by default it only provides IP and sessionID information. If we want to save more information about Http requests, You can do this by customizing WebAuthenticationDetails.

If we want to customize WebAuthenticationDetails, together with the WebAuthenticationDetailsSource redefined.

In conjunction with the captcha login from the previous article, I’ll show you an example of customizing WebAuthenticationDetails.

In the previous article we performed captcha judgments in the MyAuthenticationProvider class. Review the code from the previous article:

public class MyAuthenticationProvider extends DaoAuthenticationProvider {

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String code = req.getParameter("code");
        String verify_code = (String) req.getSession().getAttribute("verify_code");
        if (code == null || verify_code == null| |! code.equals(verify_code)) {throw new AuthenticationServiceException("Verification code error");
        }
        super.additionalAuthenticationChecks(userDetails, authentication); }}Copy the code

However, we can also do this in our custom WebAuthenticationDetails. We define the following two classes:

public class MyWebAuthenticationDetails extends WebAuthenticationDetails {

    private boolean isPassed;

    public MyWebAuthenticationDetails(HttpServletRequest req) {
        super(req);
        String code = req.getParameter("code");
        String verify_code = (String) req.getSession().getAttribute("verify_code");
        if(code ! =null&& verify_code ! =null && code.equals(verify_code)) {
            isPassed = true; }}public boolean isPassed(a) {
        returnisPassed; }}@Component
public class MyWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest.MyWebAuthenticationDetails> {
    @Override
    public MyWebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return newMyWebAuthenticationDetails(context); }}Copy the code

First we define MyWebAuthenticationDetails, due to its construction method, provided it just happened, so we can directly use the object for judging authentication code, and the judgment results to save isPassed variables. If we want to extended attributes, only need to define more in MyWebAuthenticationDetails properties, then extracted from it set to the corresponding properties, in this way, after the successful login can anytime, anywhere access to these attributes.

The last structure in MyWebAuthenticationDetailsSource MyWebAuthenticationDetails and return.

Now that the definition is complete, we can call MyAuthenticationProvider directly:

public class MyAuthenticationProvider extends DaoAuthenticationProvider {

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if(! ((MyWebAuthenticationDetails) authentication.getDetails()).isPassed()) {throw new AuthenticationServiceException("Verification code error");
        }
        super.additionalAuthenticationChecks(userDetails, authentication); }}Copy the code

Get the details directly from Authentication and call the isPassed method. Throw an exception if there is a problem.

The last question is how to use the custom MyWebAuthenticationDetailsSource instead of the system default WebAuthenticationDetailsSource, very simple, We just need to define it in SecurityConfig:

@Autowired
MyWebAuthenticationDetailsSource myWebAuthenticationDetailsSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            ...
            .and()
            .formLogin()
            .authenticationDetailsSource(myWebAuthenticationDetailsSource)
            ...
}
Copy the code

Will inject into SecurityConfig MyWebAuthenticationDetailsSource, And the configuration in the formLogin authenticationDetailsSource can use our custom WebAuthenticationDetails success.

After this customization, the original function of WebAuthenticationDetails will remain, that is, we can still use the old way to continue to get user IP and sessionId information, as follows:

@Service
public class HelloService {
    public void hello(a) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) authentication.getDetails(); System.out.println(details); }}Copy the code

Here strong type, when turning into MyWebAuthenticationDetails can.

This example can be downloaded from GitHub: github.com/lenve/sprin…

Ok, do you guys GET it? If you GET it, remember to click on it