Shiro权限管理框架粗解
一.流程
二.权限管理模型
用户(用户id,账号、密码…)
权限(权限id,权限名称,访问地址)
角色(角色id,角色名称)
角色和权限关系(角色id、权限id)
用户和角色关系(用户id、角色id)
三.粗细颗粒度
1.什么是粗颗粒度和细颗粒度
对资源类型的管理称为粗颗粒度权限管理,即只控制到菜单、按钮、方法,粗粒度的例子比如:用户具有用户管理的权限,具有导出订单明细的权限。对资源实例的控制称为细颗粒度权限管理,即控制到数据级别的权限,比如:用户只允许修改本部门的员工信息,用户只允许导出自己创建的订单明细。
2.如何实现粗颗粒度和细颗粒度
对于粗颗粒度的权限管理可以很容易做系统架构级别的功能,即系统功能操作使用统一的粗颗粒度的权限管理。
对于细颗粒度的权限管理不建议做成系统架构级别的功能,因为对数据级别的控制是系统的业务需求,随着业务需求的变更业务功能变化的可能性很大,建议对数据级别的权限控制在业务层个性化开发,比如:用户只允许修改自己创建的商品信息可以在service接口添加校验实现,service接口需要传入当前操作人的标识,与商品信息创建人标识对比,不一致则不允许修改商品信息。
四.基于url拦截
五.Shiro
1.Shiro架构
2.Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
3.SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
4.Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
5.Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
6.Realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
7.sessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
8.SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
9.CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
10.Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
六.实现
1.ShiroConfig
@Configuration
public class ShiroConfig {
@Bean
public 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;
}
/**
* 设置securityManager
* @return
*/
@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;
}
/**
* 认证设置
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
注:filterChainDefinitionMap.put(“”,“”)中的参数
anon 无需认证
authc 需要认证
user 如果使用rememberme可以直接访问
perms 必须得到资源权限
role 必须得到角色权限
2.ShiroRealm
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
@Resource
private UserRepository userRepository;
/**
* 获取用户角色和权限
*/
@Override
protected 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;
}
/**
* 登录认证
*/
@Override
protected 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;
}
}
3.实体类
实体类使用jpa建立并且实现多对多连接
1)用户类
@Entity
@Table(name="T_USER")
@Proxy(lazy = false)
public class User implements Serializable{
@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;
}
}
2)角色类
@Entity
@Table(name="T_ROLE")
@Proxy(lazy = false)
public class Role {
@Id
@Column(length = 50)
private String id;
//角色描述
private String detail;
//角色名称
private String name;
@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>();
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
@JoinTable(name = "T_USER_ROLE", joinColumns = { @JoinColumn(name = "r_id") },
inverseJoinColumns = {
@JoinColumn(name = "u_id") })
private List<User> users=new ArrayList<User>();
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;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
3)权限类
@Entity
@Table(name="T_PERMISSION")
@Proxy(lazy = false)
public class Permission {
@Id
@Column(length = 50)
private String id;
//url地址
private String url;
//url描述
private String name;
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
@JoinTable(name = "T_ROLE_PERMISSION", joinColumns = { @JoinColumn(name = "p_id") }, inverseJoinColumns = {
@JoinColumn(name = "r_id") })
private List<Role> roles=new ArrayList<>();
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;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}
4.Controller
1)登陆controller
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login1.html";
}
@PostMapping("/login")
@ResponseBody
public ResponseBo login(String username, String password, Boolean rememberMe) {
password = MD5Utils.encrypt(username, password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return ResponseBo.ok();
} catch (UnknownAccountException e) {
return ResponseBo.error(e.getMessage());
} catch (IncorrectCredentialsException e) {
return ResponseBo.error(e.getMessage());
} catch (LockedAccountException e) {
return ResponseBo.error(e.getMessage());
} catch (AuthenticationException e) {
return ResponseBo.error("认证失败!");
}
}
@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")
@ResponseBody
public User getLoginUser(){
return (User) SecurityUtils.getSubject().getPrincipal();
}
}
2)用户controller
@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";
}
@Resource
UserService userService;
@GetMapping("/register")
public String login() {
return "register.html";
}
@PostMapping("/register")
@ResponseBody
public 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")
@ResponseBody
public User getLoginUser(){
return (User) SecurityUtils.getSubject().getPrincipal();
}
}
注:@RequiresPermissions(“user:add”)以此设置权限名称