什么是权限管理
权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
根据官方的介绍,shiro提供了2个主要的功能“登录身份认证”、“访问授权”、其他的功能都是给Shiro打辅助的,比如Session管理,加密处理,缓存功能,记住我等。
shiro主要有三大功能模块:
- Subject:主体,一般指用户,代表当前正在执行操作的用户,但Subject代表的可以是人,也可以是任何第三方系统帐号。当然每个subject实例都会被绑定到SercurityManger上
- SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet),SecurityManager是Shiro核心,主要协调Shiro内部的各种安全组件,这个我们不需要太关注,只需要知道可以设置自定的Realm。
- Realms:用于进行权限信息的验证,一般需要自己实现,用户数据和Shiro数据交互的桥梁。比如需要用户身份认证、权限认证。都是需要通过Realm来读取数据。
细分功能 - Authentication:身份认证/登录(账号密码验证)。
- Authorization:授权,即角色或者权限验证。
- Session Manager:会话管理,用户登录后的session相关管理。
- Cryptography:加密,密码加密等。
- Web Support:Web支持,集成Web环境。
- Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。
- Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。
- Testing:测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
- Remember Me:记住我,登录后,下次再来的话不用登录了
SpringBoot集成shiro:
springboot中集成shiro相对简单,只需要两个类:一个是shiroConfig类,一个是CustonRealm类。
ShiroConfig类:
顾名思义就是对shiro的一些配置,相对于之前的xml配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。
CustomRealm类:
自定义的CustomRealm继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。
------------------------------------------- shiro认证实例 - 认证 ------------------------------------------
shiro的身份认证的流程,大致是这样的:
当我们调用subject.login(token)的时候,首先这次身份认证会委托给Security Manager,而Security Manager又会委托给Authenticator,接着Authenticator会把传过来的token再交给我们自己注入的Realm进行数据匹配从而完成整个认证。
1、添加maven依赖
junit
junit
4.9
commons-logging
commons-logging
1.1.3
org.apache.shiro
shiro-core
1.2.2
org.apache.shiro
shiro-spring
1.2.3
2、ShiroConfig配置(SecurityManager)
package com.example.demo.conf;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
- @Description: TODO
- @Author: Top
- @Version: V1.0
- @Date: 2020-03-02 17:08
*/
@Configuration
public class ShiroConfig {
/**
- 密码校验规则HashedCredentialsMatcher
- 这个类是为了对密码进行编码的 ,
- 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,
- 这个类也负责对form里输入的密码进行编码
- 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher
*/
@Bean(“hashedCredentialsMatcher”)
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//指定加密方式为MD5
credentialsMatcher.setHashAlgorithmName(“MD5”);
//加密次数
credentialsMatcher.setHashIterations(1);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean(“authRealm”)
@DependsOn(“lifecycleBeanPostProcessor”)//可选
public AuthRealm authRealm(@Qualifier(“hashedCredentialsMatcher”) HashedCredentialsMatcher matcher) {
AuthRealm authRealm = new AuthRealm();
authRealm.setAuthorizationCachingEnabled(false);
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}
/**
- 配置Shiro核心 安全管理器 SecurityManager
- SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组 件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
- @param authRealm
- @return
*/
@Bean(“securityManager”)
public SecurityManager securityManager(@Qualifier(“authRealm”) AuthRealm authRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
manager.setRealm(authRealm);
return manager;
}
/**
- 定义shiroFilter过滤器并注入securityManager
- @param manager
- @return
*/
@Bean(“shiroFilter”)
public ShiroFilterFactoryBean shiroFilter(@Qualifier(“securityManager”) SecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置securityManager
bean.setSecurityManager(manager);
//设置登录页面
//可以写路由也可以写jsp页面的访问路径
bean.setLoginUrl(“/login”);
//设置登录成功跳转的页面
bean.setSuccessUrl(“/pages/index.jsp”);
//设置未授权跳转的页面
bean.setUnauthorizedUrl(“/pages/unauthorized.jsp”);
// 自定义过滤器
Map<String, Filter> filters = new HashMap<>();
filters.put(“authc”, authenticationFilter());
//定义过滤器
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put(“//index", “anon”);
filterChainDefinitionMap.put("//login”, “anon”);
filterChainDefinitionMap.put(“/loginUser”, “anon”);
filterChainDefinitionMap.put(“/admin”, “roles[admin]”);
filterChainDefinitionMap.put(“/edit”, “perms[delete]”);
filterChainDefinitionMap.put(“/druid/", “anon”);
//需要登录访问的资源 , 一般将/放在最下边
filterChainDefinitionMap.put("//my/”, “authc”);
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
bean.setFilters(filters);
return bean;
}
/**
- Spring的一个bean , 由Advisor决定对哪些类的方法进行AOP代理 .
- @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
/**
- 配置shiro跟spring的关联
- @param securityManager
- @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier(“securityManager”) SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
/**
- lifecycleBeanPostProcessor是负责生命周期的 , 初始化和销毁的类
- (可选)
*/
@Bean(“lifecycleBeanPostProcessor”)
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthenticationFilter authenticationFilter() {
return new AuthenticationFilter();
}
}
3、subject.login(token)
在shiro中用Principals抽象了“身份”的概念,这里指的是我们的username,用Credentials抽象了“证明”的概念,这里指的是我们的password
4、实现Realm(用户数据和Shiro数据交互的桥梁)
package com.example.demo.conf;
import com.example.demo.common.User;
import com.example.demo.service.UserService;
import com.example.demo.utils.MD5Util;
import org.apache.shiro.authc.*;
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;
/**
- @Description: 认证授权
- @Author: Top
- @Version: V1.0
- @Date: 2020-03-02 17:09
*/
public class AuthRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
- 用户授权
- @param principals
- @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return new SimpleAuthorizationInfo();
}
/**
- 认证登录
- @param token
- @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token携带了用户信息
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
System.out.println(token.getCredentials());
System.out.println(token.getPrincipal());
System.out.println(((UsernamePasswordToken) token).getUsername());
System.out.println(((UsernamePasswordToken) token).getPassword());
String userName = token.getPrincipal().toString();
//String verificationCode = String.valueOf((char[])token.getCredentials()) ;
//获取前端输入的用户名
//根据用户名查询数据库中对应的记录
User user = userService.findByUsername(userName);
//封装用户信息,构建AuthenticationInfo对象并返回
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userName, //用户名
MD5Util.encode(user.getPassword()), //密码
getName()
);
return authenticationInfo;
}
}
密码校验流程:整体逻辑就是,先从缓存根据token取认证信息(AuthenticationInfo),若没有,则调用我们自己实现的MyRealm中的doGetAuthenticationInfo去获取,然后尝试缓存,最后再通过assertCredentialsMatch去验证token和info,assertCredentialsMatch则会根据MyReaml中setCredentialsMatcher我们设置的加密方式去进行相应的验证
上述代码可以添加用户登录时,密码错误次数校验
//访问一次,计数一次
ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
opsForValue.increment(SHIRO_LOGIN_COUNT+name, 1);
//计数大于5时,设置用户被锁定一小时
if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+name))>=5){
opsForValue.set(SHIRO_IS_LOCK+name, “LOCK”);
stringRedisTemplate.expire(SHIRO_IS_LOCK+name, 1, TimeUnit.HOURS);
}
if (“LOCK”.equals(opsForValue.get(SHIRO_IS_LOCK+name))){
throw new DisabledAccountException(“由于密码输入错误次数大于5次,帐号已经禁止登录!”);
}
以上就是shiro的身份认证实现,用户在访问anon接口的时候,可以直接访问;如果访问authc接口,则需要登录成功之后才能访问。否则会提示没有权限,需要登录。
------------------------------------------ shiro认证实例 - 授权 -----------------------------------------
Shiro的授权流程跟认证流程类似(授权是在认证基础上的操作,需要先实现了认证功能):
创建SecurityManager安全管理器
Subject主体带授权信息执行授权,请求到SecurityManager
SecurityManager安全管理器调用Authorizer授权
Authorizer结合主体一步步传过来的授权信息与Realm中的数据比对,授权
1、添加maven依赖
junit
junit
4.9
commons-logging
commons-logging
1.1.3
org.apache.shiro
shiro-core
1.2.2
org.apache.shiro
shiro-spring
1.2.3
2、ShiroConfig配置(SecurityManager)
package com.example.demo.conf;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
- @Description: TODO
- @Author: Top
- @Version: V1.0
- @Date: 2020-03-02 17:08
*/
@Configuration
public class ShiroConfig {
/**
- 密码校验规则HashedCredentialsMatcher
- 这个类是为了对密码进行编码的 ,
- 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,
- 这个类也负责对form里输入的密码进行编码
- 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher
*/
@Bean(“hashedCredentialsMatcher”)
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//指定加密方式为MD5
credentialsMatcher.setHashAlgorithmName(“MD5”);
//加密次数
credentialsMatcher.setHashIterations(1);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean(“authRealm”)
@DependsOn(“lifecycleBeanPostProcessor”)//可选
public AuthRealm authRealm(@Qualifier(“hashedCredentialsMatcher”) HashedCredentialsMatcher matcher) {
AuthRealm authRealm = new AuthRealm();
authRealm.setAuthorizationCachingEnabled(false);
authRealm.setCredentialsMatcher(matcher);
return authRealm;
}
/**
- 配置Shiro核心 安全管理器 SecurityManager
- SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组 件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
- @param authRealm
- @return
*/
@Bean(“securityManager”)
public SecurityManager securityManager(@Qualifier(“authRealm”) AuthRealm authRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
manager.setRealm(authRealm);
return manager;
}
/**
- 定义shiroFilter过滤器并注入securityManager
- @param manager
- @return
*/
@Bean(“shiroFilter”)
public ShiroFilterFactoryBean shiroFilter(@Qualifier(“securityManager”) SecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置securityManager
bean.setSecurityManager(manager);
//设置登录页面
//可以写路由也可以写jsp页面的访问路径
bean.setLoginUrl(“/login”);
//设置登录成功跳转的页面
bean.setSuccessUrl(“/pages/index.jsp”);
//设置未授权跳转的页面
bean.setUnauthorizedUrl(“/pages/unauthorized.jsp”);
// 自定义过滤器
Map<String, Filter> filters = new HashMap<>();
filters.put(“authc”, authenticationFilter());
//定义过滤器
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put(“//index", “anon”);
filterChainDefinitionMap.put("//login”, “anon”);
filterChainDefinitionMap.put(“/loginUser”, “anon”);
filterChainDefinitionMap.put(“/admin”, “roles[admin]”);
filterChainDefinitionMap.put(“/edit”, “perms[delete]”);
filterChainDefinitionMap.put(“/druid/", “anon”);
//需要登录访问的资源 , 一般将/放在最下边
filterChainDefinitionMap.put("//my/”, “authc”);
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
bean.setFilters(filters);
return bean;
}
/**
- Spring的一个bean , 由Advisor决定对哪些类的方法进行AOP代理 .
- @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
/**
- 配置shiro跟spring的关联
- @param securityManager
- @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier(“securityManager”) SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
/**
- lifecycleBeanPostProcessor是负责生命周期的 , 初始化和销毁的类
- (可选)
*/
@Bean(“lifecycleBeanPostProcessor”)
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthenticationFilter authenticationFilter() {
return new AuthenticationFilter();
}
}
3、subject.login(token)
在shiro中用Principals抽象了“身份”的概念,这里指的是我们的username,用Credentials抽象了“证明”的概念,这里指的是我们的password
4、实现Realm(用户数据和Shiro数据交互的桥梁)
package com.example.demo.conf;
import com.example.demo.entity.User;
import com.example.demo.utils.MD5Util;
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 java.util.ArrayList;
import java.util.List;
/**
- @Description: 认证授权
- @Author: Top
- @Version: V1.0
- @Date: 2020-03-02 17:09
*/
public class AuthRealm extends AuthorizingRealm {
/**
- 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取用户输入的账号
String username = (String)token.getPrincipal();
//2.通过username从数据库中查找到user实体
User user = getUserByUserName(username);
if(user == null){
return null;
}
//3.通过SimpleAuthenticationInfo做身份处理
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(user, MD5Util.encode(user.getPassword()),getName());
//4.用户账号状态验证等其他业务操作
if(!user.getAvailable()){
throw new AuthenticationException(“该账号已经被禁用”);
}
//5.返回身份处理对象
return simpleAuthenticationInfo;
}
/**
- 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取当前登录的用户
User user = (User) principal.getPrimaryPrincipal();
//通过SimpleAuthenticationInfo做授权
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//添加角色
simpleAuthorizationInfo.addRole(user.getRole());
//添加权限
simpleAuthorizationInfo.addStringPermissions(user.getPermissions());
return simpleAuthorizationInfo;
}
/**
- 模拟通过username从数据库中查找到user实体
- @param username
- @return
*/
private User getUserByUserName(String username){
List users = getUsers();
for(User user : users){
if(user.getUsername().equals(username)){
return user;
}
}
return null;
}
/**
- 模拟数据库数据
- @return
*/
private List getUsers(){
List users = new ArrayList<>(2);
List cat = new ArrayList<>(2);
cat.add(“sing”);
cat.add(“rap”);
List dog = new ArrayList<>(2);
dog.add(“jump”);
dog.add(“basketball”);
users.add(new User(“top”,“123”,true,“cat”,cat));
users.add(new User(“zoe”,“123”,true,“dog”,dog));
return users;
}
}
5、发起登录:用户信息(用户名字,密码,角色,权限 … …)注入shiro
执行该语句的时候,实际会交给AuthRealm 中的doGetAuthenticationInfo方法进行处理,这里是shiro和数据库实际交换的地方,会根据用户登录时候输入的用户名从数据库获取用户信息,注入shiro
@PostMapping(“/login”)
@ResponseBody
public Result login(HttpServletRequest request, HttpServletResponse response, @RequestBody UserParam userParam) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userParam.getUserName(), userParam.getPassword());
subject.login(token);
return new Result(“成功”,“ok”);
}
6、角色、权限赋值
执行该语句的时候,实际会交给AuthRealm中的doGetAuthorizationInfo方法进行处理,这里专门给角色、和权限进行了赋值
/**
- @Description: 获取角色
- @Param: []
- @Return: java.lang.String
- @Author: Top
- @Date: 2020-03-04 19:46
*/
@GetMapping(“/dog”)
public String dog(){
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“dog”)){
return “dog√”;
}
else {
return “dog×”;
}
}
/**
- @Description: 获取权限
- @Param: []
- @Return: java.lang.String
- @Author: Top
- @Date: 2020-03-04 19:46
*/
@GetMapping(“/rap”)
public String rap(){
Subject subject = SecurityUtils.getSubject();
if(subject.isPermitted(“rap”)){
return “rap”;
}else{
return “没权限你Rap个锤子啊!”;
}
}
授权通常解决方式有两种:
其一:登录后通过读取数据库中角色和权限,获取需要展示的菜单内容,动态的在前端渲染(动态只展示拥有的权限)
其二:所有内容都在前端写好,通过前端的shiro标签控制对应权限内容部分的渲染。(所以权限都展示出来,只有部分能操作,没有操作权限的,点击提示:没有权限)
参考资料:
https://www.jianshu.com/p/bf1f490aa70f