Spring Boot ------- integrated Shiro

Spring Boot (14) -- Integrated Shiro

23. Introduction to Shiro

23.1 what is Shiro

  • Apache Shiro is a security (permission) framework for Java
  • Shiro can easily develop good enough applications, which can be used not only in Java se environment, but also in Java EE environment
  • Shiro can complete: authentication, authorization, encryption, session management, integration with the Web, caching, etc
  • Download address

23.2 what functions are included

  • Authentication: identity authentication / login to verify whether the user has the corresponding identity
  • Authorization: authorization, i.e. permission verification, which verifies whether an authenticated user has a certain permission; That is, judge whether the user can perform any operation, such as verifying whether a user has a role. Or fine-grained verification of whether a user has certain permissions on a resource
  • Session Management: Session Management, that is, after a user logs in, it is a session. Before exiting, all its information is in the session; The session can be a normal Java se environment or a Web environment
  • Cryptography: encryption to protect the security of data. For example, the password is encrypted and stored in the database instead of plaintext
  • Web Support: Web Support can be easily integrated into the web environment
  • Caching: caching. For example, after a user logs in, it is not necessary to check his / her user information and roles / permissions every time, which can improve efficiency
  • Concurrency: Shiro supports concurrent verification of multithreaded applications, that is, if you start another thread in one thread, you can automatically propagate permissions in the past
  • Testing: provides test support
  • "Run As": allow one user to pretend to be the identity of another user (if they allow it)
  • Remember Me: Remember Me, this is a very common function, that is, after logging in once, you don't need to log in next time

23.3 Shiro external architecture

  • Subject: the object that the application code directly interacts with is subject, that is to say, the core of Shiro's external API is subject. Subject represents the current "user". This user is not necessarily a specific person. Anything interacting with the current application is a subject, such as web crawler, robot, etc; All interactions with the subject will be delegated to the SecurityManager; Subject is actually a facade, and SecurityManager is the actual executor
  • SecurityManager: Security Manager; That is, all security related operations will interact with SecurityManager; And it manages all subjects; It can be seen that it is the core of Shiro. It is responsible for interacting with other components of Shiro. It is equivalent to the role of dispatcher servlet in spring MVC
  • Real: Shiro obtains security data (such as users, roles and permissions) from real, which means that if SecurityManager wants to verify user identity, it needs to obtain corresponding users from real for comparison to determine whether the user identity is legal; You also need to get the corresponding role / authority of the user from Realm to verify whether the user can operate; You can think of Realm as a DataSource

23.4 internal structure of Shiro

  • Subject: any "user" who can interact with the application;
  • SecurityManager: equivalent to dispatcher servlet in spring MVC; It's Shiro's heart; All specific interactions are controlled through the SecurityManager; It manages all subjects and is responsible for authentication, authorization, session and cache management.
  • Authenticator: responsible for Subject authentication. It is an extension point and can be customized; Authentication Strategy can be used, that is, when the user has passed the authentication;
  • Authorizer: authorizer, that is, access controller, which is used to determine whether the subject has permission to perform corresponding operations; That is, it controls which functions users can access in the application;
  • Realm: there can be one or more realms, which can be considered as the data source of security entity, that is, the data source used to obtain security entity; It can be implemented by JDBC, memory, etc; Provided by the user; Therefore, in general, you need to implement your own realm in the application;
  • SessionManager: the component that manages the Session life cycle; Shiro can be used not only in the Web environment, but also in the ordinary Java se environment
  • CacheManager: cache controller to manage the cache of users, roles, permissions, etc; Because these data are rarely changed, putting them in the cache can improve the performance of access
  • Cryptography: password module. Shiro improves some common encryption components, such as password encryption / decryption.

23.5 Shiro project analysis

  • Rapid practice

  • View official documents: Apache Shiro Tutorial | Apache Shiro

  • Official quickstart: github.com

  • Create a maven parent project, delete unnecessary parts, and copy the following contents on the official website

  • Import Shiro dependencies

	<dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>

        <!-- configure logging -->
        <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
  • log4j.properties
log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
  • shiro.ini, you need to install an ini file plug-in in the IDEA to highlight it
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
  • Start class Quickstart
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 * Easy to get started Shiro uses the API
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        // Read configuration file:
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        // Get the current user object Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //The Session of Shiro obtained by the current user can be separated from the web stored value
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //Judge whether the current user is authenticated
        if (!currentUser.isAuthenticated()) {
            //Token token
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            //Set remember me
            token.setRememberMe(true);
            try {
                //Perform login operation
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        // Check role
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        //Coarse grain size
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        //Fine grained
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        //cancellation
        currentUser.logout();

        //end
        System.exit(0);
    }
}
  • The basic functions are Spring Security, but the name is changed in Shiro
// Get the current user object Subject
Subject currentUser = SecurityUtils.getSubject();
//The Session of Shiro obtained by the current user can be separated from the web stored value
Session session = currentUser.getSession();
//Judge whether the current user is authenticated
currentUser.isAuthenticated()
//Obtain the authentication of the current user
currentUser.getPrincipal()
//Get role permissions
currentUser.hasRole("schwartz")
//Coarse grained permissions
currentUser.isPermitted("lightsaber:wield")
//cancellation
currentUser.logout();

24. SpringBoot integrated Shiro

24.1 environmental construction

  • Create a new module, select the springboot project, and import a web and thymeleaf dependency
  • pom.xml
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • Create a new MyController and test whether the running environment is normal
@Controller
public class MyController {
    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro");

        return "index";
    }

    @RequestMapping("/user/add")
    public String add() {
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update() {
        return "user/update";
    }

}
  • New index HTML page
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
	<h1>home page</h1>
	<p th:text="${msg}"></p>

	<hr>
	<a th:href="@{/user/add}">add</a>   | <a 			th:href="@{/user/update}">update</a>
	</body>
</html>
  • Create a new add. In the user folder HTML page
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>add</h1>
</body>
</html>
  • Create a new update in the user folder HTML page
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>update</h1>
</body>
</html>
  • Project structure

  • Importing shiro to integrate spring dependencies
<!--shiro integration spring My bag-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.6.0</version>
</dependency>
  • Write custom class userrealm
//Custom realm
public class UserRealm extends AuthorizingRealm {
    //to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Authorization executed");
        return null;
    }

    //authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("Certification performed");
        return null;
    }
}
  • Write configuration class ShiroConfig
@Configuration
public class ShiroConfig {
    //ShiroFilterFactoryBean,DefaultWebSecurityManager,realm

    //Step 1: create a realm object
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
    //Step 2: DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //Associate UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //Step 3: ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //Set up security manager
        bean.setSecurityManager(defaultWebSecurityManager);
        return bean;
    }
}
  • Test. At this time, add and update can jump freely

24.2 Shiro realizes login interception

  • Add the following configuration to the getShiroFilterFactoryBean() method of ShiroConfig. The meaning of the second parameter is as follows

    • anon: access without authentication

    • authc: must be authenticated to access

    • user: you must have the remember me function to use

    • perms: you can only access a resource with permission

    • Role: having a role permission

	@Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //Set up security manager
        bean.setSecurityManager(defaultWebSecurityManager);
        //Add shiro's built-in filter
        Map<String,String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/*","authc");

        // filterMap.put("/user/add","anon");
        // filterMap.put("/user/update","authc");

        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }
  • At this time, click add or update, and the page Jump fails
  • After successful interception, jump to the login page and write login html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Login page</title>
    </head>
    <body>
        <h1>Sign in</h1>
        <hr>

        <form action="">
            <p>user name:<input type="text" name="username"></p>
            <p>password:<input type="text" name="password"></p>
            <p>password:<input type="submit"></p>
        </form>
    </body>
</html>
  • Add the toLogin() method in MyController
@Controller
public class MyController {
    ...
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

}
  • Add the login page Jump configuration in the getShiroFilterFactoryBean() method of ShiroConfig
@Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ...
        //If you do not have permission, jump to the login page
        bean.setLoginUrl("/toLogin");
        return bean;
    }
  • Test to realize the function of jumping to the login page after interception

24.3 Shiro realizes user authentication

  • Write the processing method after the user submits the form in MyController login()
@RequestMapping("/login")
    public String login(String username, String password, Model model) {
        //Get a user
        Subject subject = SecurityUtils.getSubject();
        // Encapsulated user login data
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            //Execute the login method. If there is no exception, it indicates that the login is successful
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            //user name does not exist
            model.addAttribute("msg","User name error");
            return "login";
        } catch (IncorrectCredentialsException e) {
            //Password does not exist
            model.addAttribute("msg","Password error");
            return "login";
        }
    }
  • Modify login HTML page
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Login page</title>
    </head>
    <body>
        <h1>Sign in</h1>
        <hr>

        <p th:text="${msg}" style="color: red;"></p>
        <form th:action="@{/login}">
            <p>user name:<input type="text" name="username"></p>
            <p>password:<input type="text" name="password"></p>
            <input type="submit">
        </form>
    </body>
</html>
  • During the test run, it is found that the doGetAuthenticationInfo() authentication method in UserRealm is running
  • Write the authentication in UserRealm in user authentication
	//authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("Certification performed");
        //Forge user name and password
        String name = "zzz";
        String password = "123456";
        //Judge user name and password
        //Token is the user data token encapsulated in MyController
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //User name authentication
        if(!userToken.getUsername().equals(name)){
            //Throw an exception UnknownAccountException
            return null;
        }
        //Password authentication Shiro do, do not write their own
        //The first parameter: obtain the authentication of the current user; The second parameter: the password to be passed; The third parameter: authentication name
        return new SimpleAuthenticationInfo("",password,"");
    }

24.4 Shiro integrates MyBatis

  • Import dependency
<dependencies>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.1</version>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--introduce mybatis,This is MyBatis Officially provided adaptations spring Boot Yes, not spring Boot own-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--shiro integration spring My bag-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.6.0</version>
        </dependency>
    </dependencies>
  • Configuration file application Compilation of yaml
spring:
  datasource:
    username: root
    password: 123456
    #? serverTimezone=UTC resolves the error in the time zone
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot does not inject these attribute values by default and needs to bind by itself
    #druid data source proprietary configuration
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #Configure filters for monitoring statistics interception, stat: monitoring statistics, log4j: logging, wall: defensive sql injection
    #If allowed, an error will be reported in Java lang.ClassNotFoundException: org. apache. log4j. Priority
    #Then import log4j dependency. Maven address: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
mybatis:
  type-aliases-package: com.zmt.pojo
  mapper-locations: classpath:mapper/*.xml
  • Preparation of User class
public class User {
    private int id;
    private String name;
    private String pwd;
}
  • Written by UserMapper interface
@Repository
@Mapper
public interface UserMapper {
    public User queryUserByName(String name);
}
  • UserMapper.xml Mapping
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
     PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=Bind a corresponding Dao/Mapper Interface-->
<mapper namespace="com.zmt.mapper.UserMapper">
    
        <select id="queryUserByName" parameterType="String" resultType="User">
        select * from mybatis.user where name = #{name};
    	</select>
</mapper>
  • Userserve interface implementation
public interface UserService {
    public User queryUserByName(String name);
}
  • UserServiceImpl business logic
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}
  • testing environment
@SpringBootTest
class ShiroSpringbootApplicationTests {
    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
        System.out.println(userService.queryUserByName("Li Si"));
    }
    
}
  • UserRealm connects to the real database
//Custom realm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    
    //to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Authorization executed");
        return null;
    }

    //authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("Certification performed");
        //Judge user name and password
        //Token is the user data token encapsulated in MyController
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //Connect to real database
        User user = userService.queryUserByName(userToken.getUsername());
        if(user==null){
            //UnknownAccountException
            return null;
        }
        //Password authentication Shiro does it. You don't have to write it yourself
        //The first parameter: obtain the authentication of the current user; The second parameter: the password to be passed; The third parameter: authentication name
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}

24.5 Shiro enables user authorization

  • Add authorization statements in ShiroConfig. Note that they should be written before login interception
@Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
		...
        //to grant authorization
        filterMap.put("/user/add","perms[user:add]");
        //Add shiro's built-in filter to intercept login
        filterMap.put("/user/*","authc");
		...
    }
  • Add unauthorized page in MyController
@RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized() {
        return "This page cannot be accessed without authorization";
    }
  • Add a jump to the unauthorized page in the getShiroFilterFactoryBean() method in ShiroConfig
 //Step 3: ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ...
        //If you do not have permission, jump to the login page
        bean.setLoginUrl("/toLogin");
        //Set unauthorized page
        bean.setUnauthorizedUrl("/noauth");
        return bean;
    }
  • Test, jump successful

  • To truly authorize users in UserRealm, you need to add the permission field perms in the user table and modify pojo

public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;
}
  • Modify the UserRealm class. The first parameter to be returned in the authentication method is user, return new SimpleAuthenticationInfo(user,user.getPwd(), "");
//Custom realm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    //to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Authorization executed");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //Get the currently logged in object
        Subject subject = SecurityUtils.getSubject();
        //Get user object
        User currentUser = (User)subject.getPrincipal();
        //Set current permissions
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

    //authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        ...
        //Password authentication Shiro do, do not write their own
        //The first parameter: obtain the authentication of the current user; The second parameter: the password to be passed; The third parameter: authentication name
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

24.6 Shiro integrates Thymeleaf

  • Modify ShiroConfig
//Integrate ShiroDialect: used to integrate Shiro and Thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
  • pom.xml
<!--shiro-thymeleaf integration-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
  • Modify home page code
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
        xmlns:shiro="http://www.pollox.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>home page</h1>
    <p>
        <a th:href="@{/toLogin}">Sign in</a>
    </p>
    <p th:text="${msg}"></p>
    <hr>

    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">add</a>
    </div>

    <div shiro:hasPermission="user:update">
        <a th:href="@{/user/update}">update</a>
    </div>
</body>
</html>
  • The test is successful, and the add or update page will not be displayed within the time limit

24.7 complete code

  • ShiroConfig
@Configuration
public class ShiroConfig {
    //ShiroFilterFactoryBean,DefaultWebSecurityManager,realm

    //Step 1: create a realm object
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
    //Step 2: DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //Associate UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //Step 3: ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //Set up security manager
        bean.setSecurityManager(defaultWebSecurityManager);
        //Add shiro's built-in filter
        Map<String,String> filterMap = new LinkedHashMap<>();
        //to grant authorization
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");
        //Add shiro's built-in filter to intercept login
        filterMap.put("/user/*","authc");


        // filterMap.put("/user/add","anon");
        // filterMap.put("/user/update","authc");

        bean.setFilterChainDefinitionMap(filterMap);
        //If you do not have permission, jump to the login page
        bean.setLoginUrl("/toLogin");
        //Set unauthorized page
        bean.setUnauthorizedUrl("/noauth");
        return bean;
    }

    //Integrate ShiroDialect: used to integrate Shiro and Thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}
  • UserRealm
//Custom realm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    //to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Authorization executed");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //Get the currently logged in object
        Subject subject = SecurityUtils.getSubject();
        //Get user object
        User currentUser = (User)subject.getPrincipal();
        //Set current permissions
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

    //authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("Certification performed");
        //Judge user name and password
        //Token is the user data token encapsulated in MyController
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //Connect to real database
        User user = userService.queryUserByName(userToken.getUsername());
        if(user==null){
            //UnknownAccountException
            return null;
        }
        //Password authentication Shiro do, do not write their own
        //The first parameter: obtain the authentication of the current user; The second parameter: the password to be passed; The third parameter: authentication name
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}
  • MyController
@Controller
public class MyController {
    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro");

        return "index";
    }

    @RequestMapping("/user/add")
    public String add() {
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update() {
        return "user/update";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username, String password, Model model) {
        //Get a user
        Subject subject = SecurityUtils.getSubject();
        // Encapsulate user login data
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            //Execute the login method. If there is no exception, it indicates that the login is successful
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            //user name does not exist
            model.addAttribute("msg","User name error");
            return "login";
        } catch (IncorrectCredentialsException e) {
            //Password does not exist
            model.addAttribute("msg","Password error");
            return "login";
        }

    }

    @RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized() {
        return "This page cannot be accessed without authorization";
    }

}
  • pom.xml
<dependencies>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.1</version>
    </dependency>
    <!--log4j-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <!--introduce mybatis,This is MyBatis Officially provided adaptations spring Boot Yes, not spring Boot own-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    <!--thymeleaf-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!--shiro integration spring My bag-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.6.0</version>
    </dependency>

    <!--shiro-thymeleaf integration-->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>

</dependencies>

Keywords: Java Shiro Spring Boot

Added by kinadian on Mon, 07 Mar 2022 14:32:32 +0200