Interested friends can go to understand the first few, your praise is the biggest support for me, thank you!

  • The article directories

SpringBoot takeoff road -HelloWorld

(two) SpringBoot takeoff road – analysis of the principle of entry

(three) SpringBoot takeoff road -YAML configuration summary (entry must know will)

(4) SpringBoot takeoff road – static resource processing

(five) SpringBoot takeoff road -Thymeleaf template engine

(6) SpringBoot takeoff road – integration jdbctemplate-druid-mybatis

(7) SpringBoot launch road – integration of SpringSecurity (Mybatis, JDBC, memory)

(eight) SpringBoot take-off path – integration Shiro detailed tutorial (MyBatis, Thymeleaf)

SpringBoot -Swagger 2 And 3

Description:

  • The purpose of this article is to integrate, that is, a specific practical experience, which is not involved in the original reason, and I myself have not in-depth research, so I will not make a fool of myself

  • SpringBoot takeoff road series of articles source, are synchronized to github, there is a need for small partners, go down at will

    • Github.com/ideal-20/Sp…
  • Talent and learning, will point to shallow knowledge, we right as a tool to see, do not like not angry ha ~

I first met Shiro

(1) introduction

Permissions and security problems, although not a must affect the program, project operation condition, but it is an important factor in the development, for example, we don’t want to be some resources access to or we have some methods to meet the specified identity can access, we can use the AOP or filter to implement requirements, but in fact, If the code involves more logic, the code is extremely tedious, redundant, and many development frameworks, such as Spring Security, Shiro, have provided us with this function, we just need to know how to correctly configure and use it

(2) Basic introduction

Website: http://shiro.apache.org/

Apache Shiro™ is a Powerful and Easy to Use Java Security Framework that performs authentication, authorization, Cryptography, and Session Management. With Shiro’s Easy-to-understand API, You can quickly and easily secure any application — from the smallest mobile applications to the largest Web and enterprise applications.

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, encryption, and session management. With Shiro’s easy-to-understand apis, you can quickly and easily secure any application — from the smallest mobile applications to the largest Web and enterprise applications.

A brief overview:

  • Shiro and Spring Security are the same in nature, both are a permission framework to ensure application permission Security issues
  • Shiro performs authentication, authorization, encryption, and session management, Web integration, caching, and more
  • Shiro can be applied not only to JavaEE environments, but even to JavaSE

(3) Basic functions

This part of the content, to be honest, is just a quick glance at the beginning of the code, until you have actually typed the code once, you can probably have an impression of some parts of the code, and then further research may have a better grasp

A: Official architecture drawing

  • Authentication: User Authentication refers to whether the user identity is valid. Generally, user Authentication is to verify the user name and password to determine the validity of the user identity. After confirming the validity of the user identity, the user can access the system

  • Authorization: If different users need different levels of permissions, user Authorization is involved. User Authorization controls the resources that users can access and the operations that they can perform. Different permissions are assigned according to different user roles or permissions

  • SessionManager: Shior provides a complete session management solution, which can be a normal Java SE environment, or a Web environment, but I am a bit of a rut, or a habitual way, this part of the research

  • Cryptography: encrypts plain text passwords to protect data security

  • WebSupport: literally, its support for the Web makes it very easy to integrate into the Web environment;

  • Caching: For example, after a user logs in, the user information, roles, and permissions do not need to be checked every time. This improves efficiency

  • Concurrency Concurrency for multi-threaded applications. Concurrency allows for Concurrency validation for multi-threaded applications. For example, when you start another thread in one thread, you can automatically transfer permissions to each other

  • Testing: Not much to say, just support Testing

  • Run As: Allows a user to pretend to be another user (if allowed) to access resource requests

  • Remember Me: It also has the function, Remember Me

B: Three core components

Shiro has three core components in the framework: Subject, SecurityManager, and Realms

  1. Subject is a security term, representing the authentication Subject. Generally speaking, it can be simply understood as the user of the current operation. However, the concept of user is not very accurate, because Subject is not necessarily a person, but can also be some things, such as third-party processes or scheduled operations. Things that currently interact with software.
    • Each Subject object must be managed by the SecurityManager
  2. Subject is managed by the SecurityManager, which manages the security operations of all users and internally references many security-related components, but none of them are open to the outside world. Developers use Subject more often
  3. The concept of Realms is also important, as it can be understood that when Shiro and the data are authenticated and authorized, they go to this section to find something. Realm is essentially a heavily encapsulated secure Dao

(4) the concept of user | | role permission

Since Shiro is a security permission technology, it simply means that you have a degree of control over the resources or requests that are accessed in your application. How to divide it involves three concepts: user, role, and permission

User: nothing to say, represents the current Subject authentication body, such as certain content must be logged in before the User can access it

Role: Indicates the Role and identity of a user. A Role can have multiple permissions. For example, only the administrator can access this Role

Permission: Refers to the specific rights to operate resources, such as adding, modifying, deleting, and viewing data

Add: A role is simply a collection of permissions that define what the role can do. Don’t always specify what the role can do

(2) Static page import And page environment construction

(1) About static pages

A: Page introduction

Page is my own temporary, need friends can go to my GitHub: Ideal -20 download source, a brief description of this page

Do a static page if too much trouble, you can simply create some simple pages, write a few title text, can reflect the current page is good

I use these pages in code, is to take the open source front-end component framework for a little beautify, and then convenient to explain some functions, the page template is mainly with Thymeleaf

1. Directory structure

├ ─ ─ index. HTML/ / home page├ ─ ─ images//├─ CSS ├─ JS ├─ views// Total subpage folder, the key page for permission verification│ ├ ─ ─ the login. HTML// Login page│ ├ ─ ─ success. HTML// Success page│ ├ ─ ─ unauthorized. HTML// Unauthorized page: If unauthorized users access resources, go to this page│ ├ ─ ─ L - A// l-a subpage folder containing A, B, and C subpages│ │ ├ ─ ─ a.h HTML │ │ ├ ─ ─ b.h HTML │ │ ├ ─ ─ c. the TML | ├ ─ ─ L - B// the l-b subpage folder contains a, B and C subpages│ │ ├ ─ ─ a.h HTML │ │ ├ ─ ─ b.h HTML │ │ ├ ─ ─ c. the TML | ├ ─ ─ L - C// l-c subpage folder containing a, B, and C subpages│ │ ├─ a.TML │ ├─ B.html │ ├─ A.htmlCopy the code

B: Import into the project

The main thing is to replace some basic links and introduce something with the tag format of Thymeleaf. The syntax here is not very much, and it is easy to understand even if you are not familiar with Thymeleaf. Of course, if you still feel a little difficult, you can simply make HTML. Or check out my previous post on Thymeleaf for a primer

Go to Resources > static for CSS, image, and JS, and go to Resources > templates for views and index.html

A: Introduce dependencies

It doesn’t matter if this section is imported, or if the project is initialized with auto build checked, as long as it is imported normally

  • Introduce the Spring Security module
<dependency>
	<groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.3</version>
</dependency>
Copy the code

The key dependency is mainly the above launcher, but there are some are conventional or supplementary, such as Web, Thymeleaf, DevTools, etc., and some such as Mybatis, etc. I have put in, the following dependency has been basically all, specific to a certain piece, specific again

Thymeleaf-extras-shiro, which will be mentioned later, is used to integrate Shiro with Thymeleaf

<dependencies>
	<dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.5.3</version>
    </dependency>

    <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.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
       <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
         <optional>true</optional>
   </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>
Copy the code

B: Page skip Controller

Because we used the template, the jump of the page needs to be handed over to the Controller. It is very simple, the first is the home page, of course, it doesn’t matter about the page, I randomly jump to my blog, and then there are the jump of the login page, success page and unauthorized page

There is A small Tip that needs to be mentioned, because l-A, L-B, l-C each have three pages a.HTML, B.HTML, c.HTML, so you can use @pathVariable to write A more general jump method

@Controller
public class PageController {

    @RequestMapping({"/", "index"})
    public String index(a) {
        return "index";
    }

    @RequestMapping("/about")
    public String toAboutPage(a) {
        return "redirect:http://www.ideal-20.cn";
    }

    @RequestMapping("/toLoginPage")
    public String toLoginPage(a) {
        return "views/login";
    }

    @RequestMapping("/levelA/{name}")
    public String toLevelAPage(@PathVariable("name") String name) {
        return "views/L-A/" + name;
    }

    @RequestMapping("/levelB/{name}")
    public String toLevelBPage(@PathVariable("name") String name) {
        return "views/L-B/" + name;
    }

    @RequestMapping("/levelC/{name}")
    public String toLevelCPage(@PathVariable("name") String name) {
        return "views/L-C/" + name;
    }
    
    @RequestMapping("/unauthorized")
    public String toUnauthorizedPage(a) {
        return "views/unauthorized";
    }

    @RequestMapping("/success")
    public String toSuccessPage(a) {
        return "views/success"; }}Copy the code

C: Final effect of environment building

  • I made the page a little narrower for the sake of texture
  • The upper right corner of the home page should be the login link, because I run the code that has been written, and if I do not log in the page, such as L-A-A and other modules, it cannot be displayed, so I log in with A defined administrator identity
  • How to make it automatically switch between displaying login and post-login information will be explained later

1, the first page

2. Sub-pages

In L-A, L-B, L-C, a.HTML, B.HTML, C.HTML are the same, but the text changes A little

3. Login page

4. Successful and unauthorized pages

I took a screenshot and spliced the two pages together, nothing to say, just two plain H5 pages

(3) create databases and entities

(1) Create database and table

-- ----------------------------
-- Table structure for role
-- ----------------------------
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key of role table',
  `role_name` varchar(32) DEFAULT NULL COMMENT 'Role Name'.PRIMARY KEY (`id`)
);

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1.'SUPER_ADMIN');
INSERT INTO `role` VALUES (2.'ADMIN');
INSERT INTO `role` VALUES (3.'USER');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'User primary key',
  `username` varchar(32) NOT NULL COMMENT 'Username',
  `password` varchar(32) NOT NULL COMMENT 'password',
  `role_id` int(11) DEFAULT NULL COMMENT 'Foreign key associated with role role table'.PRIMARY KEY (`id`),
  CONSTRAINT `user_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
);

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1.'BWH_Steven'.'666666'.1);
INSERT INTO `user` VALUES (2.'admin'.'666666'.2);
INSERT INTO `user` VALUES (3.'zhangsan'.'666666'.3);

-- ----------------------------
-- Table structure for permission
-- ----------------------------
CREATE TABLE `permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Permission table primary key',
  `permission_name` varchar(50) NOT NULL COMMENT 'Permission name',
  `role_id` int(11) DEFAULT NULL COMMENT 'Foreign key associated with role role table'.PRIMARY KEY (`id`),
  CONSTRAINT `permission_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
);

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1.'user:*'.1);
INSERT INTO `permission` VALUES (2.'user:*'.2);
INSERT INTO `permission` VALUES (3.'user:queryAll'.3);
Copy the code

(2) entity

In the database, the role table, the user table and the permission table have the concept of a foreign key, so the entity is written as a reference

Character class

@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Role {
    private int id;
    private String roleName;
}
Copy the code

UserPojo = UserPojo; UserPojo = UserPojo; UserPojo = UserPojo

@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class UserPojo {
    private int id;
    private String username;
    private String password;
    private Role role;
}
Copy the code

Permissions class

@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Permission {
    private Integer id;
    private String permissionName;
    private Role role;
}
Copy the code

(4) Integration of MyBatis

What I’m going to do today, in fact, it’s possible to simulate two data sets by yourself, but to get closer to reality, I’m going to introduce Mybaits

(1) Importing dependencies and configuring them

The MyBatis dependency is introduced first, and the driver dependency is also introduced

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
Copy the code

Connection pool what do not toss about, want to change their own configuration ha

spring:
  datasource:
    username: root
    password: root99
    url: jdbc:mysql://localhost:3306/springboot_shiro_test? serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: cn.ideal.pojo

server:
  port: 8080
Copy the code

I haven’t written the Mapper yet, but as we go along, we’ll write it up as the flow needs it

(2) write Mapper

Since the code was written before the article, we will use the method of username and permission later, so, we will write it as follows

@Mapper
public interface UserMapper {
    UserPojo queryUserByUsername(@Param("username") String username);

    Permission queryPermissionByUsername(@Param("username") String username);
}
Copy the code

Concrete XML configuration SQL

This section involves a slightly more complex query with multiple tables, so if you feel a bit overwhelmed, you can review the previous knowledge, or just leave it alone and move on to Shiro


      
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.ideal.mapper.UserMapper">

    <! Define a resultMap that encapsulates users and roles.
    <resultMap id="userRoleMap" type="cn.ideal.pojo.UserPojo">
        <id property="id" column="id"/>
        <result property="username" column="username"></result>
        <result property="password" column="password"></result>
        <! -- Configure to encapsulate the contents of UserPojo -->
        <association property="role" javaType="cn.ideal.pojo.Role">
            <id property="id" column="id"></id>
            <result property="roleName" column="role_name"></result>
        </association>
    </resultMap>

    <! Define a resultMap that encapsulates permission and roles.
    <resultMap id="permissionRoleMap" type="cn.ideal.pojo.Permission">
        <id property="id" column="id"/>
        <result property="permissionName" column="permission_name"></result>
        <! Configure the contents of the encapsulated Role -->
        <association property="role" javaType="cn.ideal.pojo.Role">
            <id property="id" column="id"></id>
            <result property="roleName" column="role_name"></result>
        </association>
    </resultMap>

    <select id="queryUserByUsername" resultMap="userRoleMap">
        SELECT u.*,r.role_name FROM `user` u, `role` r
          WHERE username = #{username} AND u.role_id = r.id;
    </select>

    <select id="queryPermissionByUsername" resultMap="permissionRoleMap">
        SELECT p.* ,r.role_name FROM `user` u, `role` r, `permission` p
          WHERE username = #{username} AND u.role_id = r.id AND p.role_id = r.id;
    </select>

</mapper>
Copy the code

(3) Code test

@SpringBootTest
class Springboot13ShiroMybatisApplicationTests {

    @Autowired
    private UserMapper userMapper;
    
    @Test
    void contextLoads(a) {
        UserPojo admin = userMapper.queryUserByUsername("admin");
        System.out.println(admin.toString());
        Permission permission = userMapper.queryPermissionByUsername("admin"); System.out.println(permission.toString()); }}Copy the code

(v) Spring Boot integration Shiro

(1) Custom Authentication and Authorization (Realm)

First we need to create Shiro’s configuration class, a configuration class named ShiroConfig under the config package

@Configuration
public class ShiroConfig {
	/ / 1, ShiroFilterFactoryBean
	/ / 2, DefaultWebSecurityManager
	// Realm object (custom)
}
Copy the code

As you can see from the above comment, we need to create several items in the configuration class. Since they are associated with each other, such as the Manager, the top filter is associated with the middle Manager, so we choose to write them backwards. It is more comfortable to write the latter Realm first (that is, the earliest Realm to be referenced) so that you can layer by layer after the previous one

To get a Realm, write a method in the ShiroConfig configuration class that returns an instantiated userRealm()

/** * To create a Realm object, you need to define ** yourself@return* /
@Bean
public UserRealm userRealm(a) {
    return new UserRealm();
}
Copy the code

We need to create a new class to define it

We define a UserRealm class and inherit from the AuthorizingRealm class. Then we need to implement two methods:

  • DoGetAuthenticationInfo () Authentication method: Checks whether the user passes the authentication. It can be simply interpreted as whether the login succeeds

  • DoGetAuthorizationInfo () Authorization method: Assign permissions and roles to users who have logged in successfully

It is also easy to understand from the introduction above, must be authentication first, then the authorization method will be executed, so let’s write the authentication code first

A: the authentication

The authentication first needs to obtain the data from our front desk, which is obviously handed over to the Controller. We will first complete this content, and then come back to write the authentication

Description: Obtain the data at the front desk is below the login method, invoke the authentication method at the same time, several other methods, just in order to use the late presentation, a piece of give up, at the same time, the following I captured all exceptions in the login method, we can more detailed division, because in order to demonstrate the key at the same time, I didn’t do too much reception processing, For example, if you pass in a string for login failure, you can omit it altogether

@Controller
public class UserController {
    @RequestMapping("/user/queryAll")
    @ResponseBody
    public String queryAll(a) {
        return "This is the user/queryAll method";
    }

    @RequestMapping("/user/admin/add")
    @ResponseBody
    public String adminAdd(a) {
        return "This is the user/adminAdd method";
    }

    @RequestMapping("/login")
    public String login(String username, String password, HttpServletRequest request) {
        // Since it is obtained from the name parameter, I encapsulate it here
        UserPojo user = new UserPojo();
        user.setUsername(username);
        user.setPassword(password);
        // Create a Token based on the foreground user name and password.
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        // Get the subject authentication principal (in this case, the user currently logged in)
        Subject subject = SecurityUtils.getSubject();
        try{
            // The authentication starts, and this jumps to the custom UserRealm
            subject.login(token);
            // Can be stored in session
            request.getSession().setAttribute("user", user);
            return "views/success";
        }catch(Exception e){
            // Catch an exception
            e.printStackTrace();
            request.getSession().setAttribute("user", user);
            request.setAttribute("errorMsg"."Dude, wrong username or password.");
            return "views/login"; }}}Copy the code

Authentication with UserRealm:

Description: GetPrincipal (); getPrincipal(); getPrincipal(); getPrincipal(); After the Token is changed to the UsernamePasswordToken type, the user name or password can be obtained by using getxxx

As you can see, we only need to submit the data queried in the database to Shiro for authentication, and the details are encapsulated

Supplement: UserService. QueryUserByUsername (username) method in the just call return UserMapper according to user’s query method of user information, just for the sake of structural integrity, not involved in any business, if not clear, can go to a lot to look at the source code

/** * Authentication **@param authenticationToken
 * @return
 * @throws AuthenticationException
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
	// Get the username based on the Token created in the receiving foreground
    String username = (String) authenticationToken.getPrincipal();
    // UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
    // System.out.println(userToken.getPrincipal());
    // System.out.println(userToken.getUsername());
    // System.out.println(userToken.getPassword());
    
    // Query related user information by user name (entity)
    UserPojo user = userService.queryUserByUsername(username);
    if(user ! =null) {
        // Save to Session, optional
        SecurityUtils.getSubject().getSession().setAttribute("user", user);
        // Shiro does the password authentication
        AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "userRealm");
        return authenticationInfo;
     } else {
        // If null is returned, an exception is thrown
        return null; }}Copy the code

B: authorization

Authorization is to set the user’s permission or role information after user authentication. Here, it is mainly to obtain the user name, call Mapper in service and then query the user or permission according to the user name. Because the returned is the user or permission entity object, So use getXXX and other methods to get the desired value

SetRoles () : setRoles() : setRoles() : setRoles() : setRoles() : setRoles() : setRoles() This is a single demo

/** * authorized **@param principalCollection
 * @return* /
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    // Get the user name information
    String username = (String) principalCollection.getPrimaryPrincipal();
    // Create a simple authorization authentication information
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    // Set the role information obtained from the role table for the user
    authorizationInfo.addRole(userService.queryUserByUsername(username).getRole().getRoleName());
    // Set the permission information obtained from the Permission table for this user
    authorizationInfo.addStringPermission(userService.queryPermissionByUsername(username).getPermissionName());
    return authorizationInfo;
}
Copy the code

(2) Shiro configuration

The authorization and configuration are done, that is, the Realm is done, the big head is done, and we can go back to Shiro’s configuration, work backwards, and start writing about the second point Manager

@Configuration
public class ShiroConfig {
	/ / 1, ShiroFilterFactoryBean
	/ / 2, DefaultWebSecurityManager
    
	// Realm object (custom)
	@Bean
	public UserRealm userRealm(a) {
    	return newUserRealm(); }}Copy the code

A: Configure the security manager

Then we configure the SecurityManager, where we need to introduce the Realm we just wrote so That Shiro can access the Realm and then return

/** * configures the SecurityManager SecurityManager **@return* /
 @Bean
 public DefaultWebSecurityManager securityManager(a) {
    // Add custom realms
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    / / associated Realm
    securityManager.setRealm(userRealm());
    return securityManager;
}
Copy the code

Use @qualifier as a method parameter if you have problems calling userRealm() directly when using setRealm. It will automatically look for the following public UserRealm UserRealm () method: UserRealm

@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("userRealm") UserRealm userRealm) {
    // Add custom realms
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    / / associated Realm
    securityManager.setRealm(userRealm);
    return securityManager;
}

@Bean(name="userRealm")
public UserRealm userRealm(a) {
    return new UserRealm();
}
Copy the code

B: Configure a filter

Create a ShiroFilterFactoryBean. If you want to return this object, you need to associate your securityManager with it. The next thing we need to write is the main thing, and then we need to set up some of our own definitions

  • Customize the login page
  • Success page
  • Unauthorized interface
  • A custom Map is used to store requests that need to be released or blocked
  • Logout page

Key stores URL, value stores some corresponding permissions or roles, etc. In fact, the key block is very easy to understand, for example: / CSS /** and /user/admin/** respectively represent all files in the CSS folder. The request path prefix is /user/admin/ URL, and the corresponding value has certain specifications

Key:

  • Anon: No authentication required. It’s accessible, meaning tourists can access it
  • Authc: Must be authenticated to access, i.e. after logging in for example
  • Roles [XXX] : Can be accessed only if you have a role identity. Note: XXX is a role parameter
  • Perms [XXX] : you must have the permission to access a request or resource. Note: XXX is the permission parameter

Supplement:

  • User: You must use the Remember Me function to access it
  • Logout: indicates that you can logout. After the logout command is run, the system is displayed to the configured login page
/** * Configure Shiro filter **@param securityManager
 * @return* /
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
    / / define shiroFactoryBean
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

    / / associated securityManager
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    // Customize the login page, if the login time, the request will be executed, i.e. jump to the login page
    shiroFilterFactoryBean.setLoginUrl("/toLoginPage");
    // Specify a success page
     shiroFilterFactoryBean.setSuccessUrl("/success");
    // Specify an unauthorized interface
    shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

    // LinkedHashMap is ordered for sequential interceptor configuration
    Map<String, String> filterChainMap = new LinkedHashMap<>();

    // Configure the IP address that can be accessed anonymously. You can add and permit static resources based on the actual situation. Anon indicates the permit
    filterChainMap.put("/css/**"."anon");
    filterChainMap.put("/img/**"."anon");
    filterChainMap.put("/js/**"."anon");
    // Specify page release, for example, the login page allows all users to log in
    filterChainMap.put("/toLoginPage"."anon");

    // Users starting with "/user/admin" need to be authenticated. Authc indicates authentication
    filterChainMap.put("/user/admin/**"."authc");

    filterChainMap.put("/levelA/**"."roles[USER]");
    filterChainMap.put("/levelB/**"."roles[ADMIN]");
    filterChainMap.put("/levelC/**"."roles[SUPER_ADMIN]");

    // / all requests under user/admin/ must be authenticated. Only the user:[*] permission can be accessed. You can also set the permission to user: XXX
    filterChainMap.put("/user/admin/**"."perms[user:*]");

    // Configure the logout filter
    filterChainMap.put("/logout"."logout");

    // Store the Map to the filter
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
    return shiroFilterFactoryBean;
}
Copy the code

C: Solve the problem of multiple identities

In fact, the above content has been basically sound, but there is still A very difficult problem, that is, for example, the three modules in my home page, super administrator A, B, C can access, administrator can access A and B, and ordinary users can only access A, how to write? Does it look something like this?

filterChainMap.put("/levelA/**"."roles[USER,ADMIN,SUPER_ADMIN]");
filterChainMap.put("/levelB/**"."roles[ADMIN,SUPER_ADMIN]");
filterChainMap.put("/levelC/**"."roles[SUPER_ADMIN]");
Copy the code

If you look at the role-related filter code, it is clear that roles are authenticated by hasAllRoles. That is, all roles must be accessed by hasAllRoles

/**
 * Filter that allows access if the current user has the roles specified by the mapped value, or denies access
 * if the user does not have all of the roles specified.
 *
 * @since0.9 * /
public class RolesAuthorizationFilter extends AuthorizationFilter {

    //TODO - complete JavaDoc

    @SuppressWarnings({"unchecked"})
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;

        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }

        Set<String> roles = CollectionUtils.asSet(rolesArray);
        returnsubject.hasAllRoles(roles); }}Copy the code

Create a Fileter and change the authentication mode of Role to hasRole

public class MyRolesAuthorizationFilter extends AuthorizationFilter {

    @SuppressWarnings({"unchecked"})
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;

        if (rolesArray == null || rolesArray.length == 0) {
            return false;
        }

        List<String> roles = CollectionUtils.asList(rolesArray);
        boolean[] hasRoles = subject.hasRoles(roles);
        for (boolean hasRole : hasRoles) {
            if (hasRole) {
                return true; }}return false; }}Copy the code

With this new role filter for the rule, we can go back to the configuration and set the new rule’s filter in the following three lines

// Set a custom filter
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("anyRoleFilter".new MyRolesAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Copy the code

Naturally, the original Map definition will be changed to the form of multiple roles with custom filters

// page - User needs role authentication
filterChainMap.put("/levelA/**"."anyRoleFilter[USER,ADMIN,SUPER_ADMIN]");
filterChainMap.put("/levelB/**"."anyRoleFilter[ADMIN,SUPER_ADMIN]");
filterChainMap.put("/levelC/**"."anyRoleFilter[SUPER_ADMIN]");
Copy the code

(6) Shiro integrates Thymeleaf

That’s the end of the story, but since we talked earlier about how to use It with Thymeleaf in Spring Security, I want to add a little bit more about how to use Shiro with Thymeleaf

A: the introduction of

First, introduce the dependence of integration of the two:

<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
Copy the code

This version is up to date (and very old) in 2016 and can be found on the Maven Repository website

Note: This dependency requires thymeleaf to be 3.0, and Springboot is the latest launcher we use, which is 3.0, but it’s worth mentioning

We then add this code to Shiro’s main configuration ShiroConfig class so that we can use Shiro’s custom tags in Thymeleaf

/** * integrate thymeleaf *@return* /
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(a){
    return new ShiroDialect();
}
Copy the code

B: Modify the page

After the operation, we can begin to change the page, first introduced the head restraint XMLNS: shiro = “http://www.pollix.at/thymeleaf/shiro”

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

For example, login should be displayed before login, and user name and logout should be displayed after login. If more information is needed, I suggest saving it into session. Here, I directly obtained the user name using shiro: Principal tag

<div>
   <! -- This represents other code, the following is just an excerpt -->
    
   <! -- Login and logout -->
    <div class="right menu">
      <! -- If not logged in -->
      <! --<div shiro:authorize="! isAuthenticated()">-->
      <div shiro:notAuthenticated="">
        <a class="item" th:href="@{/toLoginPage}">
          <i class="address card icon"></i>The login</a>
      </div>

      <! -- If you have logged in -->
      <div shiro:authenticated="">
        <a class="item">
          <i class="address card icon"></i>User name:<span shiro:principal></span>
          <! <span SEC :authentication="principal. Authorities "></span>-->
        </a>
      </div>

      <div shiro:authenticated="">
        <a class="item" th:href="@{/logout}">
          <i class="address card icon"></i>The cancellation</a>
      </div>
    </div> 
</div>	
Copy the code

The following is used to display only the corresponding module, for example, the user login only A can access, so B and C modules do not show him, he can not access this module anyway

<div class="ui stackable three column grid">
    <div class="column" shiro:hasAnyRoles="USER,ADMIN,SUPER_ADMIN">
      <div class="ui raised segments">
        <div class="ui segment">
          <a th:href="@{/levelA/a}">L-A-a</a>
        </div>
        <div class="ui segment">
          <a th:href="@{/levelA/b}">L-A-b</a>
        </div>
        <div class="ui segment">
          <a th:href="@{/levelA/c}">L-A-c</a>
        </div>
      </div>
    </div>
    <div class="column" shiro:hasAnyRoles="ADMIN,SUPER_ADMIN">
      <div class="ui raised segments">
        <div class="ui segment">
          <a th:href="@{/levelB/a}">L-B-a</a>
        </div>
        <div class="ui segment">
          <a th:href="@{/levelB/b}">L-B-b</a>
        </div>
        <div class="ui segment">
          <a th:href="@{/levelB/c}">L-B-c</a>
        </div>
      </div>
    </div>
    <div class="column" shiro:hasRole="SUPER_ADMIN">
      <div class="ui raised segments">
        <div class="ui segment">
          <a th:href="@{/levelC/a}">L-C-a</a>
        </div>
        <div class="ui segment">
          <a th:href="@{/levelC/b}">L-C-b</a>
        </div>
        <div class="ui segment">
          <a th:href="@{/levelC/c}">L-C-c</a>
        </div>
      </div>
    </div>
  </div>
Copy the code

C: Take a look at the effect

After the common administrator logs in, the account and logout are displayed. The C module that only the super administrator can access is not displayed

(7) Ending

If there is any deficiency in the article, you are welcome to leave a message to exchange, thank friends for their support!

If it helps you, follow me! If you prefer the way of reading articles on wechat, you can follow my official account

We don’t know each other here, but we are working hard for our dreams

A adhere to push original development of technical articles of the public number: ideal more than two days