一共5个表
- 用户表
- 角色表
- 权限表
- 用户角色中间表
- 角色权限中间表
权限验证
- 用户不跟权限直接关联, 可以给用户多个角色, 每个角色都有对应的权限,所以给用户加一个角色,就相当于,给用户赋了对应的权限。
- 一个用户可以有多个角色, 一个角色可以有多个权限
- 通过角色来判断有没有权限。 比如 用户,在用户角色中间表中有经理的角色, 就可以查看工资。这种判断比较笼统。适合实现不同的角色登录以后看到不同的菜单。
- 通过权限标识来判断, 这个就需要查出用户拥有的具体权限。 这个权限控制比较细致。
适合细致的,比如部门公司的管理员,只能修改该部门下的员工资料。
Shiro 要点
- Subject:主体,代表了当前“用户
- SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心
- Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限)
- 最简单的一个Shiro应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
Shiro 授权流程
-
1、首先调用Subject.isPermitted接口
-
2 调用相应的Realm获取主题相应的角色/权限;
-
3、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted会返回true,否则返回false表示授权失败。
架构图
实现代码
完整代码地址:https://gitee.com/bseaworkspace/springboot2all/tree/master/SpringBootShiroAuthorization
- pom
<?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"><parent><artifactId>SpringBoot2</artifactId><groupId>zz</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>SpringBootShiroAuthorization</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--目的:《可选》引入springboot 热启动,每次修改以后,会自动把改动加载,不需要重启服务了--><dependency> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.15</version></dependency><!-- 添加JPA的支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- 添加测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- shiro-spring --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version><scope>provided</scope></dependency></dependencies><repositories><repository><id>alimaven</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url></repository></repositories><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
application.properties
server.port=9087
server.servlet.context-path=/r# 数据库的信息
spring.datasource.url = jdbc:mysql://localhost:3306/java10?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username = root
spring.datasource.password = Java20190713*yy
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.jpa.database = MYSQL
# spring.jpa.show-sql = true 表示会在控制台打印执行的sql语句
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
三个实体类,
注意:
- 不要放@Data在实体类上,会报死循环错误
- 不要懒加载
权限实体类
package com.zz.entity;import lombok.Data;
import org.hibernate.annotations.Proxy;import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;/*** @Description: java类作用描述* @Author: Bsea* @CreateDate: 2019/8/31$ 15:54$*/
@Entity
@Table(name="T_PERMISSION")
@Proxy(lazy = false)
public class Permission implements Serializable {final static long serialVersionUID=23434243325424l;@Id@Column(length = 50)private String id;//url地址private String url;//url描述private String name;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getName() {return name;}public void setName(String name) {this.name = name;}}
角色实体类
package com.zz.entity;import lombok.Data;
import org.hibernate.annotations.Proxy;import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;/*** @Description: java类作用描述* @Author: Bsea* @CreateDate: 2019/8/31$ 15:54$*/
@Entity
@Table(name="T_ROLE")@Proxy(lazy = false)
public class Role implements Serializable {final static long serialVersionUID=234345424l;@Id@Column(length = 50)private String id;//角色描述private String detail;//角色名称private String name;// @ManyToMany注释表示Teacher是多对多关系的一端。// @JoinTable描述了多对多关系的数据表关系。name属性指定中间表名称,joinColumns定义中间表与Teacher表的外键关系。// 中间表Teacher_Student的Teacher_ID列是Teacher表的主键列对应的外键列,inverseJoinColumns属性定义了中间表与另外一端(Student)的外键关系。@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)@JoinTable(name = "T_ROLE_PERMISSION", joinColumns = { @JoinColumn(name = "r_id") }, inverseJoinColumns = {@JoinColumn(name = "p_id") })private Set<Permission> permissions = new HashSet<Permission>();public String getId() {return id;}public void setId(String id) {this.id = id;}public String getDetail() {return detail;}public void setDetail(String detail) {this.detail = detail;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Set<Permission> getPermissions() {return permissions;}public void setPermissions(Set<Permission> permissions) {this.permissions = permissions;}}
用户实体类
package com.zz.entity;import lombok.Data;
import org.hibernate.annotations.Proxy;import java.io.Serializable;
import java.sql.Date;
import java.util.HashSet;
import java.util.Set;import javax.persistence.*;@Entity
@Table(name="T_USER")@Proxy(lazy = false)
public class User implements Serializable{final static long serialVersionUID=23424l;@Id@Column(length = 50)private String id;//用户名private String username;private String passwd;//是否有效 1:有效 0:锁定private String status;//创建时间private Date createTime;//使用 @ManyToMany 注解来映射多对多关联关系//使用 @JoinTable 来映射中间表//1. name 指向中间表的名字//2. joinColumns 映射当前类所在的表在中间表中的外键//2.1 name 指定外键列的列名//2.2 referencedColumnName 指定外键列关联当前表的哪一列//3. inverseJoinColumns 映射关联的类所在中间表的外键// @ManyToMany注释表示Teacher是多对多关系的一端。// @JoinTable描述了多对多关系的数据表关系。name属性指定中间表名称,joinColumns定义中间表与Teacher表的外键关系。// 中间表Teacher_Student的Teacher_ID列是Teacher表的主键列对应的外键列,inverseJoinColumns属性定义了中间表与另外一端(Student)的外键关系。@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)@JoinTable(name = "T_USER_ROLE", joinColumns = { @JoinColumn(name = "u_id") },inverseJoinColumns = {@JoinColumn(name = "r_id") })private Set<Role> roles = new HashSet<Role>();public String getId() {return id;}public void setId(String id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPasswd() {return passwd;}public void setPasswd(String passwd) {this.passwd = passwd;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Set<Role> getRoles() {return roles;}public void setRoles(Set<Role> roles) {this.roles = roles;}
}
Shiro
package com.zz.config;import java.util.LinkedHashMap;import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfig {@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);shiroFilterFactoryBean.setLoginUrl("/login");shiroFilterFactoryBean.setSuccessUrl("/index");shiroFilterFactoryBean.setUnauthorizedUrl("/403");LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/register.html", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/img/**", "anon");filterChainDefinitionMap.put("/druid/**", "anon");filterChainDefinitionMap.put("/logout", "logout");filterChainDefinitionMap.put("/user/register", "anon");filterChainDefinitionMap.put("/", "anon");filterChainDefinitionMap.put("/**", "user");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(shiroRealm());securityManager.setRememberMeManager(rememberMeManager());return securityManager; } @Bean(name = "lifecycleBeanPostProcessor")public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Bean public ShiroRealm shiroRealm(){ ShiroRealm shiroRealm = new ShiroRealm(); return shiroRealm; } /*** cookie对象* @return*/public SimpleCookie rememberMeCookie() {// 设置cookie名称,对应login.html页面的<input type="checkbox" name="rememberMe"/>SimpleCookie cookie = new SimpleCookie("rememberMe");// 设置cookie的过期时间,单位为秒,这里为一天cookie.setMaxAge(86400);return cookie;}/*** cookie管理对象* @return*/public CookieRememberMeManager rememberMeManager() {//Cookie 数据存在客户端的浏览器CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());// rememberMe cookie加密的密钥 cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));return cookieRememberMeManager;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}}
- ShiroRealm
package com.zz.config;import javax.annotation.Resource;import com.zz.entity.Permission;
import com.zz.entity.Role;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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.springframework.beans.factory.annotation.Autowired;import com.zz.entity.User;
import com.zz.repository.UserRepository;import java.util.HashSet;
import java.util.List;
import java.util.Set;//import com.springboot.dao.UserMapper;
//import com.springboot.pojo.User;
@Slf4j
public class ShiroRealm extends AuthorizingRealm {@Resourceprivate UserRepository userRepository;/*** 获取用户角色和权限*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {User user = (User) SecurityUtils.getSubject().getPrincipal();String userName = user.getUsername();System.out.println("用户" + userName + "获取权限-----ShiroRealm.doGetAuthorizationInfo");SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();// 获取用户角色集Set<String> roleSet = new HashSet<String>();Set<Permission> permissionList= new HashSet<Permission>();Set<Role> roles=user.getRoles();for (Role r : roles) {roleSet.add(r.getName());permissionList.addAll(r.getPermissions());}simpleAuthorizationInfo.setRoles(roleSet);// 获取用户权限集Set<String> permissionSet = new HashSet<String>();for (Permission p : permissionList) {permissionSet.add(p.getName());}simpleAuthorizationInfo.setStringPermissions(permissionSet);return simpleAuthorizationInfo;}/*** 登录认证*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String userName = (String) token.getPrincipal();String password = new String((char[]) token.getCredentials());System.out.println("用户" + userName + "认证-----ShiroRealm.doGetAuthenticationInfo");
// User user = userMapper.findByUserName(userName);User user = userRepository.findByUsername(userName);
// User user=new User();if (user == null) {throw new UnknownAccountException("用户名错误!");}//1. MD5加密不可以破解//2. 登录比较的是,两个密文if (!password.equals(user.getPasswd())) {throw new IncorrectCredentialsException("密码错误!");}if (user.getStatus().equals("0")) {throw new LockedAccountException("账号已被锁定,请联系管理员!");}SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());return info;}}
Controller
package com.zz.controller;import javax.annotation.Resource;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import com.zz.entity.User;
import com.zz.pojo.ResponseBo;
import com.zz.service.UserService;
import com.zz.util.KeyUtil;
import com.zz.util.MD5Utils;@Controller
@RequestMapping("/user")
public class UserController {@RequiresPermissions("user:user")@RequestMapping("list")public String userList() {return "/index1.html";}@RequiresPermissions("user:add")@RequestMapping("add")public String userAdd(Model model) {model.addAttribute("value", "新增用户");return "/index1.html";}@RequiresPermissions("user:delete")@RequestMapping("delete")public String userDelete(Model model) {model.addAttribute("value", "删除用户");return "/index1.html";}@ResourceUserService userService;@GetMapping("/register")public String login() {return "register.html";}@PostMapping("/register")@ResponseBodypublic ResponseBo register(User user) {String password = MD5Utils.encrypt(user.getUsername(), user.getPasswd());user.setPasswd(password);user.setId(KeyUtil.genUniqueKey());userService.save(user);return ResponseBo.ok();}@RequestMapping("/")public String redirectIndex() {return "redirect:/index";}@RequestMapping("/index")public String index(Model model) {User user = (User) SecurityUtils.getSubject().getPrincipal();model.addAttribute("user", user);return "index1.html";}@PostMapping("/getlogin")@ResponseBodypublic User getLoginUser(){return (User) SecurityUtils.getSubject().getPrincipal();}
}