Why use Realm
In the last article, we wrote the identity information (username/password/role/permission) in the configuration file, but in actual development, these identity information should be stored in the data, so we need to customize Realm to get it from the data identity information for verification.
Custom Realm
- Define a MyRealm, inherit
AuthorizingRealm
package com.shiro.realm; import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyRealm1 extends AuthorizingRealm{ private static final transient Logger log = LoggerFactory.getLogger(Main.class); /** * Get identity information, we can get the user's permissions and role information from the database in this method */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { log.info("----------doGetAuthorizationInfo method is called----------"); String username = (String) getAvailablePrincipal(principals); //We can get permission/role information from database by username SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // permission Set<String> s = new HashSet<String>(); s.add("printer:print"); s.add("printer:query"); info.setStringPermissions(s); //Role Set<String> r = new HashSet<String>(); r.add("role1"); info.setRoles(r); return info; } /** * In this method, perform authentication */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //username String username = (String) token.getPrincipal(); log.info("username:"+username); //password String password = new String((char[])token.getCredentials()); log.info("password:"+password); //Get the username and password from the database for matching, here for the sake of aspect, the database operation is omitted if(!"admin".equals(username)){ throw new UnknownAccountException(); } if(!"123".equals(password)){ throw new IncorrectCredentialsException(); } //Authentication passed, return an identity information AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName()); return aInfo; } }
- To make the Realm we define work, we need to configure it in the configuration file (shiro-realm.ini)
#declare a realm
MyRealm1=com.shiro.realm.MyRealm1
#Specify the realms implementation of securityManager
securityManager.realms=$MyRealm1
- test
package com.shiro.realm; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Main { private static final transient Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { //Get an instance of SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currenUser = SecurityUtils.getSubject(); //if not authenticated if(!currenUser.isAuthenticated()){ UsernamePasswordToken token = new UsernamePasswordToken("admin","123"); token.setRememberMe(true); try { currenUser.login(token); } catch (UnknownAccountException uae) { log.info("No such user: " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info( token.getPrincipal() + "Incorrect password!"); } catch (LockedAccountException lae) { log.info( token.getPrincipal() + "locked, please contact administrator"); }catch (AuthenticationException ae) { //other unknown exception } } if(currenUser.isAuthenticated()) log.info("User"+currenUser.getPrincipal() +"Login successful"); //Is there a role of role1 if(currenUser.hasRole("role1")){ log.info("has role role1"); }else{ log.info("No role role1"); } //Do you have permission to print to the printer? if(currenUser.isPermitted("printer:print")){ log.info("Can print to the printer"); }else { log.info("Cannot print to printer"); } } }
Hash Algorithm Support
Generally, the passwords we store in the database are encrypted, such as performing one or more MD5 calculations on "original password + salt", and shiro provides support for hashing algorithms
package com.shiro.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; public class UserRealm extends AuthorizingRealm { private String salt = "hehe";//盐 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { // TODO Auto-generated method stub return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //Username entered by the user String username = (String) token.getPrincipal(); //If there is no such user in the database, return null and login fails if(!username.equals("xiaozhou")) return null; //Query password from database String password = "42029a889cc26562c986346114c02367"; SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes (salt), getName ()); return info; } }
There is not much difference between the realm using MD5 and the general realm. The only difference is that if the hash algorithm is not used (that is, the password is encrypted), the password queried from the database is plaintext, otherwise the ciphertext is queried. The method uses the ciphertext to directly compare and judge whether the password is correct. In order to let shiro automatically help us encrypt and then compare, we need to tell shiro what algorithm to use in the configuration file ini
[main] #password matcher credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher # matcher uses md5 credentialsMatcher.hashAlgorithmName=md5 #Do several hashes (use md5 algorithm to do several operations) credentialsMatcher.hashIterations = 1 #realm userRealm=com.shiro.realm.UserRealm #Which matcher is used by this realm userRealm.credentialsMatcher=$credentialsMatcher # Which realm to use securityManager.realms=$userRealm
Multiple Realms
Sometimes, we need to perform multiple authentications, we can define multiple Realms, like a pipeline, shiro will call the Realm in turn
MyRealm1
package com.shiro.mutilrealm; import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.shiro.realm.Main; public class MyRealm1 extends AuthorizingRealm{ private static final transient Logger log = LoggerFactory.getLogger(Main.class); @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) getAvailablePrincipal(principals); //Get permission string from database by username SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // permission Set<String> s = new HashSet<String>(); s.add("printer:print"); s.add("printer:query"); info.setStringPermissions(s); //Role Set<String> r = new HashSet<String>(); r.add("role1"); info.setRoles(r); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { log.info("MyRealm1 started authentication..."); //username String username = (String) token.getPrincipal(); log.info("username:"+username); //password String password = new String((char[])token.getCredentials()); log.info("password:"+password); //Get the username and password from the database for matching, here for the sake of aspect, the database operation is omitted if(!"admin".equals(username)){ throw new UnknownAccountException(); } if(!"123".equals(password)){ throw new IncorrectCredentialsException(); } //authentication passed AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName()); return aInfo; } }
- The codes of MyRealm2 and MyRealm1 are basically the same, just copy one copy directly. Of course, if there is demand, we can freely define and modify Realm. Here is just an example.
Configure Authenticator and AuthenticationStrategy
-
What are these two things?
Above, we have configured multiple Realms for authentication. Suppose: MyRealm1 passes the verification, and MyRealm2 fails to pass the verification. This requires defining an authentication strategy to deal with this situation. Strategy means strategy. Authenticator is the authenticator
Configuration statement (shiro-mutil-realm.ini)
#declare a realm MyRealm1=com.shiro.mutilrealm.MyRealm1 MyRealm2=com.shiro.mutilrealm.MyRealm2 #Configure the validator authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator #Configure policy # AllSuccessfulStrategy means that both MyRealm1 and MyRealm2 certifications are passed. authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy #Associate the validator with the policy authenticator.authenticationStrategy = $authcStrategy #Configure the Realm used by the validator authenticator.realms=$MyRealm2,$MyRealm1 #Set Authenticator to securityManager securityManager.authenticator = $authenticator ########################################################################## # 1. AtLeastOneSuccessfulStrategy : If one (or more) Realm validates successfully, the overall attempt is considered # to be successful. If none of the verifications succeed, the entire attempt fails. # 2. FirstSuccessfulStrategy Only the information returned by the first successfully authenticated Realm will be used. all further # Realm will be ignored. If none of the verifications succeed, the overall attempt fails # 3. AllSucessfulStrategy For the overall attempt to succeed, all configured Realms must be verified successfully. if there is no one # verifications succeed, then the overall attempt fails. # ModularRealmAuthenticator defaults to AtLeastOneSuccessfulStrategy ###########################################################################
- test
package com.shiro.mutilrealm; import java.util.List; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Main { private static final transient Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { //Get an instance of SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-mutil-realm.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currenUser = SecurityUtils.getSubject(); //if not authenticated if(!currenUser.isAuthenticated()){ UsernamePasswordToken token = new UsernamePasswordToken("admin","123"); token.setRememberMe(true); try { currenUser.login(token); } catch (UnknownAccountException uae) { log.info("No such user: " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info( token.getPrincipal() + "Incorrect password!"); } catch (LockedAccountException lae) { log.info( token.getPrincipal() + "locked, please contact administrator"); }catch (AuthenticationException ae) { //other unknown exception } } if(currenUser.isAuthenticated()) log.info("User"+currenUser.getPrincipal() +"Login successful"); // get an identity set PrincipalCollection principalCollection = currenUser.getPrincipals(); } }
The result is obvious, MyRealm1 and MyRealm2 execute in sequence
Multiple Realm validation order
-
Implicit arrangement
- When you configure multiple realms, the processing order defaults to the order you configure.
- In this case, only the realm is defined, and the realms of the securityManager are not configured.
-
Explicit arrangement
- That is, the displayed configuration securityManager.realms, then the order of execution is the order in which you configure the realm of the value.
- Display arrangement is usually more recommended.
We can simply understand that the order of multiple Realm verifications is the order of our configuration