Today, take a look at shiro, a commonly used security framework

First, take a good look at shiro’s implementation principle for later learning

Shiro (Java Security Framework)

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, password, and session management. Using Shiro's easy-to-understand apis, you can quickly and easily obtain any application, from the smallest mobile applications to the largest web and enterprise applications. Apache Shiro Developer Apache Nature Java security frameworkCopy the code

It features three core components: Subject, SecurityManager, and Realms.

Subject: indicates the current user. However, in Shiro, the concept of Subject does not just refer to people. It can also be a third-party process, a Daemon Account, or something similar. It simply means "what is currently interacting with the software." But for most purposes and uses, you can think of it as Shiro's "user" concept. Subject represents the security actions of the current user, and SecurityManager manages the security actions of all users. SecurityManager: It is the core of Shiro's framework, a typical Facade pattern through which Shiro manages internal component instances and provides various services for security management. Realm: Realm acts as a "bridge" or "connector" between Shiro and application security data. That is, when authentication (login) and authorization (access control) is performed on a user, Shiro looks up the user and their permission information from an application-configured Realm. In this sense, a Realm is essentially a security-related DAO: It encapsulates the connection details of the data source and provides related data to Shiro when needed. When configuring Shiro, you must specify at least one Realm for authentication and/or authorization. It is possible to configure multiple Realms, but at least one is required.   Shiro has built-in Realms that can connect to a large number of secure data sources (aka directories), such as LDAP, relational databases (JDBC), ini-like text configuration resources, and properties files. If the default Realm does not meet your requirements, you can also insert your own Realm implementation that represents a custom data source.Copy the code

Shiro principle analysis: The core of Shiro is the Filter in the Java Servlet specification. By configuring interceptors, the interceptor chain is used to intercept requests. If access is allowed, the request is passed. Typically, interceptors are configured for system login and logout. To login, call subjection. login(token), which is the user authentication information, and then authenticate with the doGetAuthenticationInfo method in a Realm. At this time, the authentication information submitted by the user is compared with the authentication information stored in the database. If the authentication information is consistent, the access is allowed. The cookie of this reply is planted in the browser and the session information is stored on the server. On exit, calling subject.logout() clears the callback message.

Key concepts in Shiro:

1.AnonymousFilter: This filter modifies urls that anyone can access, even without permission authentication

2. FormAuthenticationFilter: through the filter to modify the url, to verify the requested url, if not through, will be redirected back to the loginurl

3. BasicHttpAuthenticationFilter: by modifying the filter urls, require the user to have already through the authentication, if not through, will be required to be certified through the Authorization information

4.LogoutFilter: A URL modified by this filter. Once a URL request is received, the subject is immediately called to exit and redirected to redirectUrl

5. NoSessionCreationFilter: by modifying the filter urls, does not create any session

6. PermissionAuthorizationFilter: permissions interceptors, verify whether the user has permissions

7.PortFilter: Port interceptor, which will automatically redirect the port to the specified port without specifying the port access URL

8. HttpMethodPermissionFilter: rest style interceptors, configuration rest access method

9. RolesAuthorizationFilter: role of interceptors, not log in, will jump to the loginurl, unauthorized, will jump to unauthorizedUrl

SslFilter:HTTPS interceptor that can be accessed in HTTPS mode

11.UserFilter: User interceptor, which requires the user to have been authenticated, or remember me

Interception configuration description:

Anon: example /admins/**= Anon has no parameter and can be used anonymously. Authc: /admins/user/**=authc For example, admins/user/**=roles[admin], multiple parameters can be quoted and separated by commas. For example, admins/user/**=roles["admin,guest"], equivalent to the hasAllRoles() method. Perms: example /admins/user/**=perms[user:add:*]. Multiple parameters can be quoted and separated by commas, for example, /admins/user/**=perms["user:add:*,user:modify:*"], the isPermitedAll() method is used when there are multiple arguments and each argument must be passed. Rest: example /admins/user/**=rest[user], according to the request method, equivalent to /admins/user/**=perms[user:method], method is POST, get, delete, etc. Port: example/admins/user / * * = port [8081], when the request is the url of the port is not 8081 jump to schemal: / / serverName: 8081? QueryString, where schmal is the protocol HTTP or HTTPS or whatever, serverName is the host that you're accessing,8081 is the port of port in the URL configuration, and queryString is the url that you're accessing, okay? The following parameters. AuthcBasic: For example, /admins/user/**=authcBasic with no parameter indicates httpBasic authentication SSL: example /admins/user/**= SSL with no parameter indicates secure URL request. The protocol is HTTPS user: for example, /admins/user/**=user. If no parameter is specified, the user must exist. Anon, authcBasic, AuCHc, user are authentication filters, PERms, ROLES, SSL, REST, port are authorization filtersCopy the code

Shiro’s permission control is only the permission control of resources. In order to achieve the permission control of business data, it must be coupled to our specific business code. Later, I will share a solution of my company.

After a brief introduction to Shiro, let’s get down to business.

Add required dependencies

<! -- spring-data-jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <! <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <! <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> < version > 1.4.0 < / version > < / dependency > < the dependency > < groupId >. Org. Apache shiro < / groupId > < artifactId > shiro - spring < / artifactId > < version > 1.4.0 < / version > < / dependency > < the dependency > <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency> <! <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> The < version > 2.4.2.1 - RELEASE < / version > < / dependency > <! - shiro and thymelef integration plug-in - > < the dependency > < groupId > com. Making. Theborakompanioni < / groupId > < artifactId > thymeleaf extras - shiro < / artifactId > < version > 2.0.0 < / version > < / dependency >Copy the code

The YML file is adding a bit of configuration. Note that the JPA is at the same level as the previous datasource

  Create a database table using JPA
  spring:
      jpa:
        hibernate:
          ddl-auto: update
          show-sql: true
          
      thymeleaf:
        cache: false
        prefix: classpath:/templates/
        suffix: .html
        encoding: UTF-8
        content-type: text/html
        mode: HTML5
Copy the code

Database design General permission management will be designed to these five tables (user table, role table, user role intermediate table, permission table, role permission intermediate table)

1. User table:

@entity // Annotation of Entity class @table (name="sys_user") public class SysUser implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;  private String userName; private String passWord; private int userEnable; public intgetId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public int getUserEnable() {
        return userEnable;
    }

    public void setUserEnable(int userEnable) { this.userEnable = userEnable; }}Copy the code

2. Role table

@entity // Annotation of Entity class @table (name="sys_role") public class SysRole implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;  private String roleName; public intgetId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) { this.roleName = roleName; }}Copy the code

3. Intermediate table of user roles

@entity // Annotation of Entity class @table (name="sys_user_role") public class SysUserRole implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int  id; private int userId; private int roleId; public intgetId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getRoleId() {
        return roleId;
    }

    public void setRoleId(int roleId) { this.roleId = roleId; }}Copy the code

4. Permission table

@entity // Annotation of Entity class @table (name="sys_permission")
public class SysPermission implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String userName;

    private String resUrl;

    private String userType;

    private String parentId;

    private String userSort;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getResUrl() {
        return resUrl;
    }

    public void setResUrl(String resUrl) {
        this.resUrl = resUrl;
    }

    public String getUserType() {
        return userType;
    }

    public void setUserType(String userType) {
        this.userType = userType;
    }

    public String getParentId() {
        return parentId;
    }

    public void setParentId(String parentId) {
        this.parentId = parentId;
    }

    public String getUserSort() {
        return userSort;
    }

    public void setUserSort(String userSort) { this.userSort = userSort; }}Copy the code

5. Intermediate table of role permissions

@entity // Annotation of Entity class @table (name="sys_role_permission")
public class SysRolePermission implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private int roleId;

    private int permissionId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getRoleId() {
        return roleId;
    }

    public void setRoleId(int roleId) {
        this.roleId = roleId;
    }

    public int getPermissionId() {
        return permissionId;
    }

    public void setPermissionId(int permissionId) { this.permissionId = permissionId; }}Copy the code

The structure of the five tables is basically like this. If there is any problem, we can change it. Once the entity class is built, we can also build the database first.

Shiro configuration class

@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); / / not login user can only access landing page shiroFilterFactoryBean. SetLoginUrl ("/auth/login"); / / to jump after a successful login link shiroFilterFactoryBean setSuccessUrl ("/auth/index"); // Unauthorised interface; - no egg use in this configuration, the specific reason want to deep understanding to baidu shiroFilterFactoryBean. SetUnauthorizedUrl ("/auth/err"); // Custom interceptor Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>(); shiroFilterFactoryBean.setFilters(filtersMap); // Permission control map. map <String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/css/**"."anon");
        filterChainDefinitionMap.put("/js/**"."anon");
        filterChainDefinitionMap.put("/img/**"."anon");
        filterChainDefinitionMap.put("/auth/login"."anon");
        filterChainDefinitionMap.put("/auth/logout"."logout");
        filterChainDefinitionMap.put("/auth/kickout"."anon");
        //filterChainDefinitionMap.put("/book/**"."authc,perms[book:list],roles[admin]");
        //filterChainDefinitionMap.put("/ * *"."authc,kickout");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // Set realm.securityManager.setrealm (myShiroRealm()); / / a custom cache implementations use redis securityManager. SetCacheManager (cacheManager ()); / / custom session management using redis securityManager. SetSessionManager (our sessionManager ());returnsecurityManager; } /** * authentication realm; (This needs to write by yourself, account password verification; Permissions, etc.) * * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        returnmyShiroRealm; } /** * The cacheManager cache Redis implementation * uses the Shiro-Redis open source plugin ** @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        returnredisCacheManager; } /** * Shiro redisManager is configured using the shiro-Redis open source plugin ** @return
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("localhost"); redisManager.setPort(6379); redisManager.setExpire(1800); Redismanager.settimeout (0); // redisManager.setPassword(password);returnredisManager; } /** * SessionManager */ @bean public DefaultWebSessionManagersessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        returnsessionManager; } /** * Shiro sessionDao shiro sessionDao shiro sessionDao shiro sessionDao shiro sessionDaoredisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        returnredisSessionDAO; } /*** * Configuration for authorization ** @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        returndefaultAdvisorAutoProxyCreator; *< dependency> *<groupId>org.springframework.boot</groupId> in the POM file *<artifactId>spring-boot-starter-aop</artifactId> *</dependency> * @param securityManager * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        returnauthorizationAttributeSourceAdvisor; } / processor * * * * * Shiro life cycle / @ Bean public LifecycleBeanPostProcessorgetLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public ShiroDialect shiroDialect() {
        returnnew ShiroDialect(); }}Copy the code

Custom Realm:

public class MyShiroRealm extends AuthorizingRealm { private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class); // If the item is used in the project, the @autoWired annotation invalidates the item. You can get the value @autoWired private SysRoleService; @Autowired private UserService userService; /** * Protected AuthenticationInfo ** / @override protected AuthenticationInfodoGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        logger.info("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- perform Shiro certificate authentication -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; String name = token.getUsername(); String password = String.valueOf(token.getPassword()); SysUser user = new SysUser(); user.setUserName(name); user.setPassWord(password); SysUser userList = userService.getUser(user); SysUser userList = userservice.getuser (user);if(userList ! = null) {// The user is disabledif(userList.getUserEnable() ! = 1) { throw new DisabledAccountException(); } logger.info("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - Shiro certificate authentication success -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userList, // userlist.getPassword (), // password getName() //realm name);returnauthenticationInfo; } throw new UnknownAccountException(); } / / Override protected AuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- perform Shiro access to -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
        Object principal = principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        if (principal instanceof SysUser) {
            SysUser userLogin = (SysUser) principal;
            Set<String> roles = roleService.findRoleNameByUserId(userLogin.getId());
            authorizationInfo.addRoles(roles);

            Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId());
            authorizationInfo.addStringPermissions(permissions);
        }

        logger.info("---- Obtain the following permissions ----");
        logger.info(authorizationInfo.getStringPermissions().toString());
        logger.info("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - Shiro access to success -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
        returnauthorizationInfo; } / * * * to remove all user authorization information cache. * / public void clearCachedAuthorizationInfo (String principal) {SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName()); clearCachedAuthorizationInfo(principals); } /** * Clears all user authorization information cachesclearAllCachedAuthorizationInfo() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if(cache ! = null) {for(Object key : cache.keys()) { cache.remove(key); }}} /** * @description: TODO clear the cached authorization information * @returnVoid Return type */ public voidclearAuthz(){ this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals()); }}Copy the code

Start testing by inserting a few pieces of data into the database.

Take the previous code to do the test, the first configuration in the configuration of the need to do permission filtering path, and permission rules

This should be set to login to access, browser directly access

When I visited the previous link, I found that it could be accessed normally and checked the previous test data

Before, we have set the user to ‘admin’ and ‘test’ two roles, did not set the role of ‘demo’, the request should also be blocked

Sure enough, I jumped to the login page, removed the “Demo”, and found that the normal request to check the data

In addition to configuring filtering rules in Shiro’s configuration file, you can also add permissions to controller via annotations to achieve the same effect

“AND” OR “OR” can be set in the box in the figure, that is, when setting multiple roles, whether to satisfy all OR one of them is enough

The granularity of role-based permission Settings is relatively coarse. You can set them in detail for each function, which is when the permission table is used

We also used the previous test to set two permissions. The permission we set in the database was’ book:* ‘, and the test found that there was no problem and it could be requested

In addition to this configuration, annotations can also be used, similar to the role configuration

This permission configuration is almost the same, there is a case is to the page in the button and so on permission control, practice is actually relatively simple

To test using HTML under Thymelef, the required JARS have already been imported, the ShiroDialect configuration file has already been configured in Shiro config, and all that remains is to introduce XMLNS in the header of the page

<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
Copy the code

Put two buttons on the page, set different permissions, change the database permissions to “book:list”, and see what happens

        <tr>
            <td colspan="2">
                <button shiro:hasPermission="book:list" type="reset"</button> <button shiro:hasPermission="book:add" type="button" onclick="submit1()"</button> </td> </tr>Copy the code

It is found that only buttons with permissions can be displayed, and a look at the page source code found that buttons without permissions are not generated in the page at all

To summarize, shiro should have no problems with basic usage, so let’s take a look at how Shiro does single sign-on (SSO).