Spring Boot Shiro Tutorial

Apache Shiro already famous, do not do not know Java, similar to .Net form of authentication certification. .Net core with authentication and authorization policies in basically the same. Of course, I do not know does not matter, because the social behavior of all permissions are simulated person or organization.

This series of permissions from simple talk, mainly related to Shiro, Spring Security, Jwt, OAuth2.0 and other custom rights policy.

This chapter explains the basic principles and how to use Shiro, this chapter uses the following infrastructure:

  • jdk1.8 +
  • spring boot 2.1.6
  • idea 2018.1

The project source code download

1 Spring Boot rapid integration Shiro example

First, we have to lower some real code demonstrates how to integrate Spring Boot Shiro. This sample code is not used to being database-related knowledge, this code is primarily used to:

  1. shiro
  2. thymeeaf

This example demonstrates the site admin user password 123456 users Username Password Log website, after Shiro authentication, access authorization permissions list, demonstrates the use of privileges.

The project source code download

1.1 New Spring Boot Maven example projects

  1. File> New> Project, select the figure below Spring Initializrand then click [Next] Next
  2. Fill GroupId(package name), Artifact(project name) can be. Click Next
    groupId = com.fishpro
    artifactId = shiro
  3. The choice depends Spring Web Starterfront tick.
  4. Project name is set spring-boot-study-shiro.

1.2 dependence introduction Pom.xml

This code is used primarily to:

  1. shiro
  2. thymeeaf

Adding the following code in Pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fishpro</groupId>
    <artifactId>shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shiro</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--shiro 1.4.0 thymeleaf-extras-shiro 2.0.0 组合-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--shiro for thymeleaf 生效需要加入 spring boot 2.x 请使用 2.0.0 版本 否则使用 1.2.1版本-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.3 Configuration application.yml

This example uses the default configuration, the configuration is not required at the shiro in application.yml. We application.properties changed application.yml (habit), modified the default port

server:
  port: 8086

1.4 Custom Realm field UserRealm implement custom authentication and authorization

In the src / main / java / com / java / fishpro / shiro / config (config is a new package name) new UserRealm.java file
UserRealm is a secure data source, user login authentication is implemented in such a core, the user such authorization is also implemented, specifically to see the code comments

  1. Rewrote doGetAuthenticationInfoa user name and password for authentication, and return SimpleAuthenticationInfothe object. * Note that because shiro is a security framework, specific identification of the certification necessary to give ourselves to achieve, in fact, certification is business logic, the best we own realization.
  2. Rewritten doGetAuthorizationInfoto implement authorization for the current user, and returns an SimpleAuthorizationInfoobject note that the authorization is to query the database from business systems currently known to the permissions list of users, write in the current session to use the time to do the match, the match is successful express authorization is successful, the match fails represent not authorized
//定义一个实体对象用于存储用户信息
public class UserDO {
    private Integer id;
    private String userName;//就是 shiro 中的身份,系统中唯一的存在
    private String password; //就是 shiro 中的证明

    public Integer getId() {
        return id;
    }

    public void setId(Integer 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;
    }
 
}

Establish UserRealm in the package config (not on the new config) under

package com.fishpro.shiro.config;

import com.fishpro.shiro.domain.UserDO;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;

import java.security.Principal;
import java.util.*;

/**
 * 用户领域 重写了 AuthorizingRealm ,AuthorizingRealm(授权) 其实是继承了 AuthenticatingRealm(认证)
 * 所在在这里只要继承 AuthorizingRealm(授权),主要实现 授权和认证的方法重写
 * 1.doGetAuthenticationInfo 重写认证
 * 2.doGetAuthorizationInfo 重写授权
 * */
public class UserRealm extends AuthorizingRealm {
    /**
     * doGetAuthenticationInfo 重写认证
     * @param authenticationToken token
     * @return 返回认证信息实体(好看身份和证明) AuthenticationInfo
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username=(String)authenticationToken.getPrincipal();//身份 例如 用户名
        Map<String ,Object> map=new HashMap<>(16);
        map.put("username",username);
        String password  =new String((char[]) authenticationToken.getCredentials());//证明 例如 密码
        //对身份+证明的数据认证 这里模拟了一个数据源
        //如果是数据库 那么这里应该调用数据库判断用户名密码是否正确
        if(!"admin".equals(username) || !"123456".equals(password)){
            throw new IncorrectCredentialsException("账号或密码不正确");
        }
        //认证通过
        UserDO user=new UserDO();
        user.setId(1);//假设用户ID=1
        user.setUserName(username);
        user.setPassword(password);
        //建立一个 SimpleAuthenticationInfo 认证模块,包括了身份】证明等信息
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
        return info;
    }

    /**
     * 重写授权 doGetAuthorizationInfo 返回  授权信息对象 AuthorizationInfo
     * @param  principalCollection 身份信息
     * @return  返回  授权信息对象 AuthorizationInfo
     * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        UserDO userDO  = (UserDO)principalCollection.getPrimaryPrincipal();
        Integer userId= userDO.getId();//转成 user 对象
        //授权 新建一个授权模块 SimpleAuthorizationInfo 把 权限赋值给当前的用户
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //设置当前会话拥有的角色 实际场景根据业务来如从数据库获取角色列表
        Set<String> roles=new HashSet<>();
        roles.add("admin");
        roles.add("finance");
        info.setRoles(roles);

        //设置当前会话可以拥有的权限 实际场景根据业务来如从数据库获取角色列表下的权限列表
        Set<String> permissions=new HashSet<>();
        permissions.add("system:article:article");
        permissions.add("system:article:add");
        permissions.add("system:article:edit");
        permissions.add("system:article:remove");
        permissions.add("system:article:batchRemove");
        info.setStringPermissions(permissions);
        return  info;
    }

}

1.6 shiro realized login authentication

Here is the main display login.html and LoginController

New file resources / templates / login.html means the login page, where logic is implemented using jquery

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用 shiro 登录页面</title>
</head>
<body>
<div>
    <input id="userName" name="userName" value="">
</div>
<div>
    <input id="password" name="password" value="">
</div>
<div>
    <input type="button" id="btnSave"  value="登录">
</div>
<script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script>
<script>
    $(function() {
        $("#btnSave").click(function () {
            var username=$("#userName").val();
            var password=$("#password").val();
            $.ajax({
                cache: true,
                type: "POST",
                url: "/login",
                data: "userName=" + username + "&password=" + password,
                dataType: "json",
                async: false,
                error: function (request) {
                    console.log("Connection error");
                },
                success: function (data) {
                    if (data.status == 0) {
                        window.location = "/index";
                        return false;

                    } else {
                        alert(data.message);
                    }

                }
            });
        });
    });
</script>
</body>
</html>

1.7 shiro Controller layer implement authorization method

It should increase the number of pages to achieve this function

1.7.1 resources / templates / index.html After a successful jump landing page

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>通过登录验证后跳转到此页面</title>
</head>
<body>
    通过登录验证后跳转到此页面
<div>
    <a href="/article">前往文章页面</a>
</div>
    <div>
        <a href="/setting">前往设置页面</a>
    </div>
</body>
</html>

1.7.2 resources / templates / article.html authorized to access the page

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>必须获取 app:article 授权</title>
 </head>
 <body>
 必须获取 app:article 授权 才会显示
 </body>
 </html>

1.7.3 resources / templates / setting.html unauthorized access page

 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>必须获取 app:setting 授权 </title>
 </head>
 <body>
 必须获取 app:setting 授权 才会显示
 </body>
 </html>

1.7.4 resources / templates / error / 403.html unauthorized unified blow page

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>403 没有授权</title>
 </head>
 <body>
 你访问的页面没有授权
 </body>
 </html>

1.7.5 controller / UserController.java Controller layer method

As source code

  1. Methods article needs permission app: article: article to enter
  2. The method of setting the permissions required app: setting: setting in order to enter

@Controller
public class UserController {
    //shiro 认证成功后默认跳转页面
    @GetMapping("/index")
    public String index(){
        return "index";
    }

    @GetMapping("/403")
    public String err403(){
        return "403";
    }
    /**
     * 根据权限授权使用注解 @RequiresPermissions
     * */
    @GetMapping("/article")
    @RequiresPermissions("app:article:article")
    public String article(){
        return "article";
    }

    /**
     * 根据权限授权使用注解 @RequiresPermissions
     * */
    @GetMapping("/setting")
    @RequiresPermissions("app:setting:setting")
    public String setting(){
        return "setting";
    }
    @GetMapping("/login")
    public String login(){
        return "login";
    }

    @PostMapping("/login")
    @ResponseBody
    public Object loginsubmit(@RequestParam String userName,@RequestParam String password){
        Map<String,Object> map=new HashMap<>();
        //把身份 useName 和 证明 password 封装成对象 UsernamePasswordToken
        UsernamePasswordToken token=new UsernamePasswordToken(userName,password);
        //获取当前的 subject
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(token);
            map.put("status",0);
            map.put("message","登录成功");
            return map;
        }catch (AuthenticationException e){
            map.put("status",1);
            map.put("message","用户名或密码错误");
            return map;
        }

    }
}

1.8 shiro achieve front page authorization

We used Thymeleaf as a template engine front-end, you can also use JSP, FreeMarker and other engines. Shiro has been able to very good use in Thymeleaf in, I use the following code in your Home

Shiro can authorize the use of a variety of ways
| ways | Notes | Examples |
| --- | --- | --- |
| Verify Login | @RequiresAuthentication | @RequiresAuthentication |
| if I remember | @RequiresUser ||
| whether a visitor | @RequiresGuest | @RequiresGuest |
| whether possessed character | @RequiresRoles | @RequiresRoles ( "ADMIN") |
| has permission | @RequiresPermissions | @RequiresPermissions ( "perm" ) |

Why are my comments not take effect?

To use shiro annotations to authorize Controller approach, you need to add the following code in the ShiroConfig

/**
     *  开启shiro aop注解支持 如@RequiresRoles,@RequiresPermissions
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

1.9 Logout / logout

Invokes the logout method subject of the write-off

@GetMapping("/logout")
    String logout(HttpSession session, SessionStatus sessionStatus, Model model) {
        //会员中心退出登录 当使用这两属性session属性退出
        session.removeAttribute("userData");
        sessionStatus.setComplete();
        SecurityUtils.getSubject().logout();
        return "redirect:/login";

    }

1.10 problem

  1. @RequiresPermissionsNotes invalid
    notes invalid, did not come to comment, the basic problem is the AOP interceptors need to increase the allocation in ShiroConfig configuration
  1. spring boot shiro Not authorized to invoke method when @RequiresPermissionsthe authority does not happen when
    @RequiresPermissionssince the entry into force, then why would an error it stands to reason already logged in, but do not have permission method body, you should jump to / 403 page fishes.
    There should also be no method to intercept this error. This in exception handling Spring Boot global in said before, you need to catch an exception mechanism to capture this exception org.apache.shiro.authz.UnauthorizedException, and then do centrally.
@ControllerAdvice(annotations = Controller.class)
public class MyExceptionController {
    private static final Logger logger= LoggerFactory.getLogger(MyExceptionController.class);
    public static final String DEFAULT_ERROR_VIEW = "error";

    @ExceptionHandler(value = UnauthorizedException.class)//处理访问方法时权限不足问题
    public String defaultErrorHandler(HttpServletRequest req, Exception e)  {
        return "error/403";
    }
}
  1. shiro: hasPermission tag does not take effect in question in thymeleaf

shiro: hasPermission label application in thymeleaf, as it relates to two frames, if the original is not supported, for example, to the introduction of third-party controls.

/**
     * ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
  1. Sometimes return wrong org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shiroDialect'

This should be a third-party plug-ins com.github.theborakompanioni and spring boot version compatibility issues. I changed to take effect the following versions shiro for thymeleaf need to add spring boot 2.x use version 2.0.0

 <!--shiro 1.4.0 thymeleaf-extras-shiro 2.0.0 组合-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--shiro for thymeleaf 生效需要加入 spring boot 2.x 请使用 2.0.0 版本 否则使用 1.2.1版本-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

Guess you like

Origin www.cnblogs.com/fishpro/p/spring-boot-study-study.html