Shiro登录权限
shiro是一个轻量级的安全框架,可以使用注解快速的实现权限管理。
权限管理
权限管理属于系统安全的范畴, 权限管理实现对用户访问系统的控制。按照安全规则或则是安全策略, 控制用户可以访问且只能访问自己被授权的资源。
权限管理包括用户身份验证和授权两部分, 简称认证授权, 对用户进行身份认证,认证通过后才可以访问可以访问的资源
认证
认证是关于验证的凭据,如用户名/用户ID和密码,以验证用户身份。
一般通过账户管理实现,账户管理和用户管理相关,在简单系统中用户和账户混淆使用。在标准系统中,用户和账户相互关联,账户是用于认证的凭据,而用户是标识账户身份的信息。
授权
授权发生在系统成功验证用户身份后,根据用户拥有权限终会授予用户访问的资源。
一般和角色关联,角色是一组服务的集合。在有些简单系统中,角色和用户组的概念混淆使用。在标准系统中,用户组和角色有关联也有区别,用户组用于描述用户群集,而角色用于描述服务群集,给用户组或用户赋予角色的过程就是授权。
实现认证和授权的整体思路
客户端访问服务端,服务端对请求进行认证,主要包括用户名和密码是否正确,如果认证成功会给客户端颁发一个凭证token,后面客户端再访问服务端的时候都要携带这个token,如果不携带或者token被篡改,都会认证失败!如果token认证成功,客户端访问资源,那么此时服务端还会去认证该用户是否有访问此资源的权利!如果此用户没有访问这个资源的权利,同样会访问失败!
导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
jwt:JSON Web Token,由请求头、请求体和签名3部分组成,它主要用来认证。
配置Shiro的Config
@Configuration
public class ShiroConfig {
@Resource
private IFunctionInfoService functionInfoService;
/**
* 配置Shiro核心 安全管理器 SecurityManager
* SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
*
* @return
*/
@Bean
public ShiroFilterFactoryBean webFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
// Map<K,V> K指的是拦截的url V值的是该url是否拦截
Map<String, String> filterChainMap = new LinkedHashMap<String, String>(16);
// 配置不需要进行身份认证的路径,其中anon表示不需要认证
filterChainMap.put("/users/login", "anon");
filterChainMap.put("/swagger-ui.html", "anon");
filterChainMap.put("/webjars/**", "anon");
filterChainMap.put("/swagger-resources/**", "anon");
filterChainMap.put("/v2/api-docs", "anon");
List<FunctionInfoBean> functionInfoBeans = functionInfoService.list();
for(FunctionInfoBean functionInfoBean : functionInfoBeans){
if(functionInfoBean.getFuncPath() != null && !functionInfoBean.equals("")){
filterChainMap.put(functionInfoBean.getFuncPath(),"perms["+functionInfoBean.getFuncName()+"]");
}
}
// 配置需要进行身份认证的路径。/**表示所有路径
filterChainMap.put("/**", "authc");
// 当认证失败后跳转的路径
//设置拦截请求后跳转的URL.
shiroFilterFactoryBean.setUnauthorizedUrl("/authorization_fail");
shiroFilterFactoryBean.setLoginUrl("/authentication_fail");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
@Bean
public CustomRealm customRealm(){
return new CustomRealm();
}
/**
* 开启aop注解支持
* 即在controller中使用 @RequiresPermissions("user/userList")
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
//设置安全管理器
attributeSourceAdvisor.setSecurityManager(securityManager);
return attributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
}
自定义Realm 认证和授权方法
自定义Realm是实现权限的关键,Realm继承Shiro框架的AuthorizingRealm抽象类,然后重写doGetAuthorizationInfo方法,在这个方法中,我们先是从客户端携带的token中取出用户名,然后根据用户名在数据库中查出该用户所拥有的角色和角色所拥有的权限,然后分别将角色和权限放到SimpleAuthorizationInfo对象中。
public class CustomRealm extends AuthorizingRealm {
@Resource
private IUserInfoService userInfoService;
@Resource
private IFunctionInfoService functionInfoService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前登录的用户
UserInfoBean userInfoBean = (UserInfoBean) principalCollection.getPrimaryPrincipal();
//通过SimpleAuthenticationInfo做授权
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 得到当前用户包含的所有角色
List<RoleInfoBean> roleInfoBeans = userInfoBean.getRoleInfoBeans();
List<FunctionInfoBean> functionInfoBeans = functionInfoService.selectByRoleId(roleInfoBeans);
for (RoleInfoBean role : roleInfoBeans) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
}
for (FunctionInfoBean function : functionInfoBeans) {
//添加权限
simpleAuthorizationInfo.addStringPermission(function.getFuncName());
}
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 身份认证的操作
//1.获取用户输入的账号
String username = (String) authenticationToken.getPrincipal();
//2.通过username从数据库中查找到user实体
UserInfoBean userInfoBean = userInfoService.selectByUserName(username);
if (userInfoBean == null) {
return null;
}
//3.通过SimpleAuthenticationInfo做身份处理
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(userInfoBean, userInfoBean.getUserPass(), getName());
//4.返回身份处理对象
return simpleAuthenticationInfo;
}
}
登录的Controller
@ApiOperation("登录方法")
@PostMapping("/login")
public Object login(String userName, String userPass) throws GlobalHandleException {
// 把用户输入的账号和密码封装到shiro框架提供的token对象中
UsernamePasswordToken token = new UsernamePasswordToken(userName, DigestUtils.sha256Hex(userPass));
Subject currentUser = SecurityUtils.getSubject();
try{
//主体提交登录请求到SecurityManager
currentUser.login(token);
}catch(IncorrectCredentialsException ice){
throw new GlobalHandleException(ResultCode.USER_PASS_ERROR);
}catch (UnknownAccountException uae){
throw new GlobalHandleException(ResultCode.USER_NOT_EXIST);
}catch (AuthenticationException ae){
throw new GlobalHandleException(ResultCode.USER_AUTHENTICATION_ERROR);
}catch (AuthorizationException ae){
throw new GlobalHandleException(ResultCode.USER_AUTHORIZATION_ERROR);
}
return currentUser.getPrincipal();
}
Shiro注解
-
RequiresAuthentication:
-
使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
-
RequiresGuest:
使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
-
RequiresPermissions:
当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
-
RequiresRoles:
当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
-
RequiresUser:
当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。