This is the 26th day of my participation in the August More Text Challenge

1. Introduction

  • Apache Shiro is a security (permissions) framework for Java;

  • Shiro can easily develop applications that are good enough to work in both JavaSE and JavaEE environments;

  • Main functions: Authentication, Authorization, encryption, call management, Web integration, caching, etc.

Shiro architecture

  • Subject: The object with which the application code interacts directly is Subject, which means that Shiro’s external API is the core of Subject. Subject represents the current “user”, who is not necessarily a specific person. Anything that interacts with the current application is Subject, such as web crawler, robot, etc. All interactions with the Subject are delegated to the SecurityManager; The Subject is the facade, and the SecurityManager is the actual performer;
  • SecurityManager: SecurityManager. That is, all security-related operations interact with the SecurityManager; And it manages all subjects; As you can see, it is the core of Shiro, responsible for interacting with Shiro’s other components, and it corresponds to the role of the DispatcherServlet in SpringMVC
  • Realm: Shiro obtains security data from realms (such as users, roles, and permissions). To authenticate users, the SecurityManager needs to retrieve users from realms for comparison. You also need to get the user’s role/permissions from Realm to verify that the user can operate. You can think of a Realm as a DataSource

2. Integrated development with Spring

2.1 Shiro certification

2.1.1 Configuration File

web.xml

<! -- Shiro filter definition -->  
	<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
		<! -- this defaults to false, indicating that the life cycle is defined by SpringApplicationContext; Set to true to ServletContainer management -->
			<param-name>targetFilterLifecycle</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>shiroFilter</filter-name>
		<url-pattern>/ *</url-pattern>
	</filter-mapping>
Copy the code

applicationContext.xml

    <! -- 1. SecurityManager SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"/>
    </bean>

    <! --2. Configure the JAR package and configuration file for CacheManager 2.1 to be added to EhCache.
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    </bean>

    <! -- 3. Custom Realm -->
    <bean id="myRealm" class="com.xiaojian.shiro.realms.MyRealm"/>


    <! Shiro bean lifecycle can be called by a bean that implements Shiro's internal lifecycle function.
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <! -- 5. Open the Shiro annotation, you must configure the lifecycleBeanPostProcessor -- -- >
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <! -- 5.Shiro filter ID must be the same as <filter-name> where DelegatingFilterProxy is configured in the web. XML file because Shiro queries the filter corresponding to <filter-name> in the IOC container bean -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <! Shiro's core security interface, this property is required -->
        <property name="securityManager" ref="securityManager"/>
        <! -- Authentication fails, skip to login page configuration -->
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="success.jsp"/>
        <property name="unauthorizedUrl" value="unauth.jsp"/>

        <! -- Shiro connection constraint configuration, i.e., filter chain definition -->
        <property name="filterChainDefinitions">
            <value>
                /login.jsp=anon
                /**=authc
            </value>
        </property>
    </bean>
Copy the code

MyRealm.java

public class MyRealm extends AuthorizingRealm{
	
	@Resource
	private BloggerService bloggerService;
	
	/** * AuthorizationInfo: permission set for a role * get authorization information */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	/** * AuthenticationInfo: user role set * login authentication * token: token, based on user name password */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("Execute authentication logic");

        // Assume the user name and password
        String username = "xiaojian";

        // 1. Convert the type to obtain the Token
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        // 2. Compare user names
        if(! token.getUsername().equals(username)){// The user name does not exist
            return null;    //// Shiro throws a UnknownAccountException
        }
        // 3. Build the AuthenticationInfo object based on the user. The implementation class is usually used: SimpleAuthenticationInfo
        // 3.1 Principal: indicates the information about the authentication entity. The value can be username or the corresponding entity-class object
        Object principal = null;
        // 3.2 hashedCredentials: Password
        Object hashedCredentials = "1234"; //a20b8e682a72eeac0049847855cecb86
        // 3.3 realName: The name of the current Realm object, calling the parent getName() method
        String realmName = getName();

        SimpleAuthenticationInfo simpleAuthenticationInfo = null;
       simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,hashedCredentials,realmName);

        returnsimpleAuthenticationInfo; }}Copy the code

2.1.2 Default filters in Shiro

The filter class Filter name example
anon No parameter, anonymous access /login.jsp=anon
authc No parameter, authentication (login) is required to access /admin/**=authc
user If no parameter is specified, the user must exist
perms There can be multiple parameters. If there are multiple parameters, they must be quoted and separated by commas. When there are multiple arguments, each argument must be passed, equivalent to the isPermitedAll() method /admin/*=perms[user:add] /admin/**=perms[“user:add,user:update”]
roles Role filter to determine whether the current user has specified a role. Same rule as above. Equivalent to the hasAllRoles() method /admin/**=roles[“admin,guest”]
logout When you log out, you accomplish something: Any existing session will be invalidated and any identity will be disconnected (in your Web application, RememberMe cookies will be deleted)

2.1.3 URL Matching Mode

Use Ant style mode, Ant path wildcard support? , *, **, wildcard matches do not include directory separator “/”

  • ? : Matches a character, such as /admin? , matching: /admin1; Mismatch: /admin123, /admin/
  • * : Matches zero or more strings or a path
  • ** : matches zero or more paths in a path

2.1.4 URL Matching Sequence

The first match is preferred. So generally, path access for /* is placed later.

2.1.5 Shiro encryption

Password match in Shiro Authentication: Password match using the credentialsMatcher in AuthorizingRealm.

1, MD5 encryption (irreversible)

(1). How to encrypt a string into MD5;

(2). Replace the credentialsMatcher property of the current Realm with the HashedCredentialsMather object and set the encryption algorithm.

applicationContext.xml

    <! Create a bean that implements a Realm interface
    <bean id="myRealm" class="com.xiaojian.shiro.realms.MyRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <! -- Encryption mode -->
                <property name="hashAlgorithmName" value="MD5"/>
                <! -- encryption times -->
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
Copy the code

In this case, the server automatically encrypts the password sent from the browser using MD5 encryption 1024 times.

2, MD5 salt value encryption

The applicationcontext.xml remains the same

(1). DoGetAuthenticationInfo returns the value to create SimpleAuthenticationInfo, using the constructor:

​ SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName);

(2). Use ByteSource. Util. Bytes (username); Encryption salt value

(3). Salt value should be unique, and random string and user ID are generally used

(4). Use new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations). Calculates the encrypted value of the salt value.

   /** * Perform authentication logic */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("Execute authentication logic");

        // 1. Convert the type to obtain the Token
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        String username = token.getUsername();

        //3. Depending on the user, building the AuthenticationInfo object returns, usually using the implementation class SimpleAuthenticationInfo
// 3.1 Principal: indicates the information about the authentication entity. The value can be username or the corresponding entity-class object
        Object principal = username;
// 3.2 hashedCredentials: Password
        Object hashedCredentials = null; //a20b8e682a72eeac0049847855cecb86
        // 2. Compare the user name and password
        if("user".equals(username)){
            hashedCredentials = "3e042e1e3801c502c05e13c3ebb495c9";
        } else if("admin".equals(username)){
            hashedCredentials = "c34af346c89b8b03438e27a32863c9b5";
        }
// 3.3 credentialsSalt: encrypted salt value
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);
// 3.4 realName: The name of the current Realm object, calling the parent getName() method
        String realmName = getName();

        SimpleAuthenticationInfo simpleAuthenticationInfo =  new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName);

        return simpleAuthenticationInfo;
    }
Copy the code

MD5Test.java

public class MD5Test {
    public static void main(String[] args) {
        String hashAlgorithmName = "MD5";
        Object credentials = "1234";
        Object salt = "admin";
        int hashIterations = 1024;

        Object result = newSimpleHash(hashAlgorithmName, credentials, salt, hashIterations); System.out.println(result); }}Copy the code

2.1.6 more Reaml

Why use multi-realm?

Different authentication is required according to different login requirements. For example: mobile phone number login, email login…

Using ModularRealmAuthenticator classes, attribute Collection realms; Inject the Realm collection

Secondrealm. Java, like the MyRealm class, changes the authentication password to the encrypted value of SHA1.

applicationContext.xml

    <bean id="secondRealm" class="com.xiaojian.shiro.realms.SecondRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <! -- Encryption mode -->
                <property name="hashAlgorithmName" value="SHA1"/>
                <! -- encryption times -->
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
Copy the code
AuthenticationStrategy

The default implementation of the AuthenticationStrategy interface:

  • FirstSuccessfulStrategy: If one Realm has been authenticated successfully, only the first Realm has been authenticated successfully.
  • AtLeastOneSuccessfulStrategy: as long as there is a Realm authentication is successful, unlike FirstSuccessfulStrategy, will return all Realm authentication is successful authentication information;
  • AllSuccessfulStrategy: All Realm validations are successful, and all Realm validations are returned. If one Realm fails, it will fail.
  • ModularRealmAuthenticator default is AtLeastOneSuccessfulStrategy strategy

Configuration:

<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<! -- <property name="realms">-->
<! -- <list>-->
<! -- <ref bean="myRealm"/>-->
<! -- <ref bean="secondRealm"/>-->
<! -- </list>-->
<! -- </property>-->
    <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
    </property>
</bean>
Copy the code

With multiple Realms, you can configure the Authenticator to the SecurityManager

Typically, all realms are configured to the SecurityManager, SecurityManager

after<! -- 1. SecurityManager SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="authenticator" ref="authenticator"/>
        
        <property name="realms">
            <list>
                <ref bean="myRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>        
    </bean>
Copy the code

2.2 Shiro authorization

Multi-realm implementation authorization, one pass, both pass.

Shiro filter added role interceptor /user.jsp = roles[user] /admin.jsp = roles[admin]Copy the code

MyRealm.java

/** * Execute authorization logic */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("MyRealm performs authorization logic");
    // 1. Get user information from principalCollection (the user name or user object, depending on what principal parameter you put in when authenticating)
    Object principal = principalCollection.getPrimaryPrincipal();
    // 2. Use the login user information to obtain the role or permission of the current user
    Set<String> roles = new HashSet<>();
    roles.add("user");
    if("admin".equals(principal)){
        roles.add("admin");
    }
    // 3. Create SimpleAuthorizationInfo and set its roles property
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    authorizationInfo.setRoles(roles);

    // return SimpleAuthorizationInfo
    return authorizationInfo;
}
Copy the code

2.3 Shiro label

Shiro provides JSTL tags for permission control on JSP pages, such as displaying page buttons based on the logged in user.

The label describe The sample
guest If the user is not authenticated, the corresponding information is displayed, that is, visitor information
user The user has been authenticated /Remember that IThe corresponding information is displayed after login
authenticated The user has been authenticated, that is, the subject.login login is successful.Not to remember that I’m logged in
notAuthenticated The user is not authenticated, that is, not logged in by calling subject.login, including remembering that I logged in automatically.
pincipal Displays user identity information, called by default
hasRole The body content is displayed if the Subject currently has a role
hasAnyRoles The body content is displayed if the current Subject has any role (or relationship).
lacksRole The body content is displayed if the Subject has no role
hasPermission The body body content is displayed if the current Subject has permission
lacksPermission The body content is displayed if the Subject does not have permissions