I. Introduction to Shiro

Apache Shiro is a security framework for Java. Powerful, using a simple Java security framework, it provides developers with an intuitive and comprehensive solution for authentication, authorization, encryption and session management.

The basic function points of Shiro are as follows:

Shiro’s workflow is as follows:

Shiro’s internal architecture is as follows:

  • Shiro’s official website
  • Authentication and Shiro security framework

Spring Boot integration with Shiro

This article implementation source code is as follows, welcome Star and Fork.

Github.com/just-right/…

2.1 Separation of front and rear ends

When a user logs in, the token information is generated, the expiration time is set, and the token information is stored in Redis. When the front-end invokes the interface, the token is passed to the server as a parameter, and the server authenticates the user according to the token information.

  • Reference link 1: understand at a glance! Springboot +Shiro +VUE front and back end separate permission management system
  • Reference link two: Everyone open source -renren-fast

To customize the AuthFilter filter, inherit AuthenticatingFilter and override the createToken, isAccessAllowed, onAccessDenied, and onLoginFailure methods.

The AuthenticatingFilte class executeLogin method is as follows:

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
	AuthenticationToken token = this.createToken(request, response);
	if (token == null) {
		String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
		throw new IllegalStateException(msg);
	} else {
		try {
			Subject subject = this.getSubject(request, response);
			subject.login(token);
			return this.onLoginSuccess(token, subject, request, response);
		} catch (AuthenticationException var5) {
			return this.onLoginFailure(token, var5, request, response); }}}Copy the code

The old Token information is deleted when the user logs in and the Token information is generated again. The Token information is deleted when the user logs out. When Redis is used to store Token information, both the information that the user ID is the key and the Token is the value and the user ID is the value are stored.

// User login --
String oldToken = tokenService.getUserToken(uid);
/** * Delete old Token information * {Token: userId} * {userId: [tokenList]} */
tokenService.delUserToken(uid);
tokenService.delTokenUser(oldToken);
/** * Create a new Token message */
String token = tokenService.createUserToken(uid);
tokenService.createTokenUser(uid,token);
Copy the code
// The user logs out --
/** * Delete Token information */
tokenService.delUserToken(Integer.valueOf(uid));
tokenService.delTokenUser(token);
Copy the code

The overall implementation flow chart is as follows.The source of the figure is from reference link 1.

2.2 Multi-Realm Management

Implementation approach: custom ModularRealmAuthenticator management Realm, combining with the custom authentication Token associated different Realm.

See Link 1: SpringBoot Shiro Multi-realm for secure-free login

The SecurityManager and ModularRealmAuthenticator configuration is as follows:

@Bean(value = "securityManager")
public SessionsSecurityManager securityManager(@Qualifier("myRealm") Realm myRealm,@Qualifier("myRealm2") Realm myRealm2) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // Specify multi-realm to configure Authenticator first - the order cannot be reversed
    securityManager.setAuthenticator(modularRealmAuthenticator());
    List<Realm> list = Arrays.asList(myRealm,myRealm2);
    securityManager.setRealms(list);
    securityManager.setRememberMeManager(null);
    return securityManager;
}

@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(a) {
    / / own ModularRealmAuthenticator rewrite
    MyModularRealmAuthenticator modularRealmAuthenticator = new MyModularRealmAuthenticator();
    // At least one success
    modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
    return modularRealmAuthenticator;
}
Copy the code

To customize authentication tokens, override the getCredentials method to return different comparison objects based on loginType.

/** * Custom Token *@author: luffy
 */
@Data
@NoArgsConstructor
public class MyAuthToken extends UsernamePasswordToken {
    private String token;
    private String loginType;

    public MyAuthToken(final String username, final String password,final String token,
                     final String loginType) {
        super(username, password);
        this.token = token;
        this.loginType = loginType;
    }

    /** * Grandfather class -- AuthenticationToken * Object getPrincipal(); -- Resource Object * Object getCredentials(); -- Comparison object * 1. Return password * 2 for common login. Return Token if Token access * Currently there are only two realms *... . *@return* /
    @Override
    public Object getCredentials(a) {
        if (IShiroConst.TOKEN_REALM_NAME.equals(this.getLoginType())){
            return getToken();
        }
        returngetPassword(); }}Copy the code

The normal login Realm authentication logic looks like this:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    MyAuthToken token = (MyAuthToken) authenticationToken;
    User user =  userService.queryByUserName(token.getUsername());
    if (user == null) {throw new UnknownAccountException("The user does not exist!");
    }
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), user.getPassword(), getName());
    if(user.getSalt() ! =null){
        info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
    }
    return info;
}
Copy the code

The Realm authentication logic associated with the Token is as follows:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    MyAuthToken authToken = (MyAuthToken) authenticationToken;
    String token = authToken.getToken();
    /** * Obtain the token from the cache. The token is the key and the UID is value */
    if(StringUtils.isEmpty(token) || ! TokenUserRedisUtils.isExistedKey(token) || StringUtils.isEmpty(TokenUserRedisUtils.getValueByKey(token))){throw new IncorrectCredentialsException("Token invalid, please log in again");
    }
    String uid = TokenUserRedisUtils.getValueByKey(token);
    assertuid ! =null;
    User user =  userService.queryById(Integer.parseInt(uid));
    if (user == null) {throw new UnknownAccountException("The user does not exist!");
    }
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, token, getName());
    return info;
}
Copy the code

Custom ModularRealmAuthenticator, management Realm, realize the logic is as follows:

/** * Multi-realm configuration Management *@author: luffy
 */
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        // Check if getRealms() returns null
        assertRealmsConfigured();
        // Cast back to the custom CustomizedToken
        MyAuthToken userToken = (MyAuthToken) authenticationToken;
        // Login type
        String loginType = userToken.getLoginType();
        / / all Realm
        Collection<Realm> realms = getRealms();
        // All realms corresponding to the login type
        List<Realm> typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
            if(realm.getName().contains(loginType)) { typeRealms.add(realm); }}// Check whether it is single or multiple realms
        if (typeRealms.size() == 1) {return doSingleRealmAuthentication(typeRealms.get(0), userToken);
        }
        else{
            returndoMultiRealmAuthentication(typeRealms, userToken); }}}Copy the code

Three, test,

After the user logs in, the token information is returned:

User Carrying Token information query article (Have corresponding authority) :

Delete a user with token information (Without permission) :

User logs out with token information:

After a user logs out, the user is deleted with the original token information: