Shiro(二):SpringBoot整合Shiro

shiro主要有三大功能模塊:

1. Subject:主體,一般指用戶。

2. SecurityManager:安全管理器,管理所有Subject,可以配合內部安全組件。(類似於SpringMVC中的DispatcherServlet)

3. Realms:用於進行權限信息的驗證,一般需要自己實現。

3.細分功能

1. Authentication:身份認證/登錄(賬號密碼驗證)。

2. Authorization:授權,即角色或者權限驗證。

3. Session Manager:會話管理,用戶登錄後的session相關管理。

4. Cryptography:加密,密碼加密等。

5. Web Support:Web支持,集成Web環境。

6. Caching:緩存,用戶信息、角色、權限等緩存到如redis等緩存中。

7. Concurrency:多線程併發驗證,在一個線程中開啓另一個線程,可以把權限自動傳播過去。

8. Testing:測試支持;

9. Run As:允許一個用戶假裝爲另一個用戶(如果他們允許)的身份進行訪問。

10. Remember Me:記住我,登錄後,下次再來的話不用登錄了。

先看項目結構

依賴的包  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>

  <groupId>org.example</groupId>
  <artifactId>SpringBootShiro0508</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>SpringBootShiro0508</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.10.RELEASE</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <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.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <repositories>
    <repository>
      <id>public</id>
      <name>aliyun nexus</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>public</id>
      <name>aliyun nexus</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>

</project>

三個實體類User,Role,Permissions

package org.example.bean;

import lombok.Data;

import java.util.Set;

@Data
public class User {
    private String id;
    private String userName;
    private String password;
    /*用戶的角色集合  用戶角色一對多*/
    private Set<Role> roles;

    public User() {
    }


    public User(String id, String userName, String password, Set<Role> roles) {
        this.id = id;
        this.userName = userName;
        this.password = password;
        this.roles = roles;
    }
}
package org.example.bean;

import lombok.Data;

import java.util.Set;
@Data
public class Role {
    private String id;
    private String roleName;
    /*角色對應的權限集合  角色權限一對多*/
    private Set<Permissions> permissions;

    public Role() {
    }

    public Role(String id, String roleName, Set<Permissions> permissions) {
        this.id = id;
        this.roleName = roleName;
        this.permissions = permissions;
    }

}
package org.example.bean;

import lombok.Data;

import java.io.Serializable;

@Data
public class Permissions implements Serializable {
    private String id;
    private String permissionsName;

    public Permissions() {
    }

    /*有個這個構造器才能在創建這個對象時傳兩個參數  創建對象時初始化*/
    public Permissions(String id, String permissionsName) {
        this.id = id;
        this.permissionsName = permissionsName;
    }


}

Service以及他的實現

package org.example.service;

import org.example.bean.User;

public interface LoginService {
    User getUserByName(String name);
}
package org.example.service.impl;

import org.example.bean.Permissions;
import org.example.bean.Role;
import org.example.bean.User;
import org.example.service.LoginService;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Service
public class LoginServiceImpl implements LoginService {
    @Override
    public User getUserByName(String getMapByName) {
        /*模擬數據庫查詢 正常情況下應該是從數據庫或者緩存查詢*/
        return getMapByName(getMapByName);
    }

    /*模擬數據庫查詢  參數:用戶名*/
    private User getMapByName(String userName){
        /*總共添加兩個用戶test和test1,admin一個角色,user一個角色*/
        /*test用戶是admin角色有query有add權限,test1用戶是user角色只有query權限*/
        Permissions permissions1 = new Permissions("1","query");
        Permissions permissions2 = new Permissions("2", "add");
        Set<Permissions> permissionsSet = new HashSet<>();
        permissionsSet.add(permissions1);
        permissionsSet.add(permissions2);

        Role role = new Role("1","admin",permissionsSet);
        Set<Role> roleSet = new HashSet<>();
        roleSet.add(role);

        User user = new User("1", "test", "123456", roleSet);
        Map<String, User> map = new HashMap<>();
        map.put(user.getUserName(),user);
        /*-------3號只有查詢權限,權限集合在角色裏,角色集合在用戶裏------------------------------------------------------------------------------------*/
        Permissions permission3 = new Permissions("3", "query");
        Set<Permissions> permissionsSet1 = new HashSet<>();
        permissionsSet1.add(permission3);
        Role role1 = new Role("2", "user", permissionsSet1);
        Set<Role> roleSet1 = new HashSet<>();
        roleSet1.add(role1);
        User user1 = new User("2", "test1", "123456", roleSet1);
        map.put(user1.getUserName(),user1);
        return map.get(userName);
    }
}

自定義Realm用於查詢用戶的角色和權限信息並保存到權限管理器:

CustomRealm.java

package org.example.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.example.bean.Permissions;
import org.example.bean.Role;
import org.example.bean.User;
import org.example.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;

public class CustomRealm extends AuthorizingRealm {
    @Autowired
    private LoginService loginService;

    /*下面沒做授權AuthorizationInfo的話,就會賬號能登陸進去,但是不能進入index,全都error沒有通過權限驗證!  都去統一異常處理那裏*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /*獲取登陸用戶名*/
        String name = (String) principalCollection.getPrimaryPrincipal();
        /*根據用戶名去數據庫查詢用戶信息*/
        User user = loginService.getUserByName(name);
        /*添加角色和權限*/
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (Role role:user.getRoles()){
            //添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
            //添加權限
            for (Permissions permissions:role.getPermissions()){
                simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
            }
        }
        return simpleAuthorizationInfo;
    }

    /*認證  登陸時*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /*post請求時先認證,然後再請求*/
        if(authenticationToken.getPrincipal()==null){
            return null;
        }
        //獲取用戶信息
        String name = authenticationToken.getPrincipal().toString();
        User user = loginService.getUserByName(name);
        if (user == null){
            //這裏返回後會報出對應異常
            return null;
        }else{
            //這裏認證authenticationToken和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name,user.getPassword().toString(),getName());
            return simpleAuthenticationInfo;
        }
    }
}

把CustomRealm和SecurityManager等加入到spring容器:

ShiroConfig.java:

package org.example.config;

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.example.shiro.CustomRealm;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


import java.util.HashMap;

@Configuration
public class shiroConfig {
    //不加這個註解不生效  具體不詳
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultApp = new DefaultAdvisorAutoProxyCreator();
        defaultApp.setProxyTargetClass(true);
        return defaultApp;
    }

    //將自己的驗證方式加入容器
    @Bean
    public CustomRealm myShiroRealm(){
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }

    //權限管理,配置主要是Realm的管理認證
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    //Filter工廠  設置對應的過濾條件和跳轉條件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        HashMap<String, String> map = new HashMap<>();
        //登出
        map.put("/logout","logout");
        //對所有用戶認證
        map.put("/**","authc");
        //登錄
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首頁
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //錯誤頁面 認證不通過跳轉
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //加入註解的使用,不加入這個註解不生效
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

LoginController.java:

package org.example.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.example.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {
    @RequestMapping("/login")
    public String login(User user) {
        //添加用戶認證信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUserName(), user.getPassword());
        try {
            subject.login(usernamePasswordToken);
 /*           subject.checkRole("admin");
            subject.checkPermissions("query", "add");*/
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return "賬號或者密碼錯誤!";
        } catch (AuthorizationException e) {
            e.printStackTrace();
            return user.getUserName()+"--賬號沒有權限!";
        }
        return user.getUserName()+"--login success!";
    }
    //註解驗角色和權限
    /*下面這段相當於Subject subject = SecurityUtils.getSubject();
                if(subject.hasRole(“admin”)) {
                //有權限
                } else {
                //無權限
                }
       不加下面兩句的話只要是正確的賬號,正確的密碼都能進入index  */
    @RequiresRoles("admin")
    @RequiresPermissions("add")
    @RequestMapping("/index")
    public String index() {
        return "index!";
    }
}

註解驗證角色和權限的話無法捕捉異常,從而無法正確的返回給前端錯誤信息,所以我加了一個類用於攔截異常,具體代碼如下

MyExceptionHandler.java

package org.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.tomcat.util.http.parser.Authorization;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
    @ExceptionHandler
    @ResponseBody
    public String ErrorHandler(AuthorizationException e){
        log.error("error沒有通過權限驗證!",e);
        return "error沒有通過權限驗證!";
    }
}

http://localhost:8080/login?userName=test&password=123456

http://localhost:8080/index

-----------------------------------------------------------------------------

http://localhost:8080/login?userName=test1&password=123456

http://localhost:8080/index

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章