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