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: