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
-----------------------------------------------------------------------------