Shiro主要用来进行权限管理。以前都是直接调用,没有对业务进行理解,当前我们自己重写了一遍Shiro的配置框架,现在记下来,以便学习。
一、概念
Shiro是一个安全框架,可以进行角色、权限管理。
Shiro主要功能如下:
Authentication(认证):用户身份识别,通常被称为用户“登录”
Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
二、主要的类
1.Subject:当前用户,Subject可以是一个人,也可以是第三方服务
2.SecurityManager:管理所有Subject,可以配合内部安全组件。
3.principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
4.credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。
5.Realms:用于进行权限信息的验证,需要自己实现。
6.Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。
在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
使用方式:
第一步:首先我们需要将对应的依赖包给导入pom.xml当中
org.apache.shiro
shiro-spring
1.3.2
第二步:我们需要创建一个Shiro的配置类,将需要拦截的访问请求拦截下来交给Shiro进行用户权限管理
package com.example.system.shiro;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Shiro配置方法类
* @author Administrator
*/
@Configuration
public class ShiroConfig {
/**
* Shiro业务过滤器配置方法
* @param securityManager
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
System.err.println("进入Shiro业务过滤配置");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
Map filterChainDefinitionMap = new LinkedHashMap<>();
//
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/User/**", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/front/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/admin/**", "authc");
filterChainDefinitionMap.put("/user/**", "authc");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 安全控制器
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
System.err.println("进入security安全控制器");
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
return defaultSecurityManager;
}
/**
* 之定义域属性
* @return
*/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
System.err.println("进入customRealm属性域控制器");
return customRealm;
}
}
第三步:我们需要继承Shiro的AuthorizingRealm(父类授权管理)的方法,对用户进行登陆管理和权限认证。
package com.example.system.shiro;
import org.apache.shiro.SecurityUtils;
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 java.util.HashSet;
import java.util.Set;
/**
* Shiro授权数据管理
* @author Administrator
*
*/
public class CustomRealm extends AuthorizingRealm {
/**
* 用户授权验证(目前没用到)
* (源码注解内容大致为:从基础数据存储中检索给定主体的授权信息,pamar是主题信息主要标识的主题)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.err.println("用户权限验证!!!!!!!!!!");
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set stringSet = new HashSet<>();
stringSet.add("user:show");
stringSet.add("user:admin");
info.setStringPermissions(stringSet);
return info;
}
/**
* 获取即将需要认证的信息
* 这里可以注入userService进行数据库验证,为了方便演示,我这里写死了帐号了密码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.err.println("--------身份认证方法- -------");
String userName = (String) authenticationToken.getPrincipal();
System.err.println("获取的用户名为:"+userName);
String userPwd = new String((char[]) authenticationToken.getCredentials());
//根据用户名从数据库获取密码
String password = "12311";
if (userName == null) {
throw new AccountException("用户名不正确");
} else if (!userPwd.equals(password )) {
throw new AccountException("密码不正确");
}
return new SimpleAuthenticationInfo(userName, password,getName());
}
}
第四步:我们在用户登陆业务接口中进行Shiro中的Subject.login调用即可。
/**
* 用户登陆业务接口
* @return
*/
@RequestMapping(value = "login")
@ResponseBody
public String userLogin() {
String username="小明123";
String password="12311";
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token= new UsernamePasswordToken(username, password);
// 执行认证登陆
try {
subject.login(token);
Session session = subject.getSession();
session.setTimeout(1200000L);//手动设置过期时间20分钟
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return "用户名或密码不正确!";
}
if (subject.isAuthenticated()) {
return "登录成功";
} else {
token.clear();
return "登录失败";
}
}
至此,目前登陆模块已经完结。Shiro的用户login会话会跳转到doGetAuthenticationInfo的方法当中对用户信息进行业务验证。
其封装方法会生成一个Sesssion和一个Cookie数据一个放到服务器一个返给前端进行保存。
每次前端进行业务访问的时候,(以下为我猜测部分,若不对,请好心提醒一下)Shiro内置方法会通过解析http消息内容中的Cookie数据来寻找服务器中有无对应的Session数据,若存在,则接口放行,更新Session有效期;若通过Cookie值没有获取到对应的Session值,则进行拦截。
这里开始补更新一下进行权限管理功能的方法和问题
在用户登陆时进行subject.login后,调用subject.isPermitted(权限名称)【看需求,也可以先判断权限再判断帐号密码】来写入用户的对应权限信息。
业务会进入doGetAuthorizationInfo方法中,对对用户的权限信息情况做查找,将用户信息数据写入Set
使用方法
可以在接口中调用 @RequiresRoles("权限名")标签,即可拦截非该权限用户访问,若费改权限用户访问即返回异常数据。
异常统一处理类
package com.example.system.shiro;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 用户权限访问异常业务捕获
* @author Administrator
* @date 2019年6月29日
*/
@ControllerAdvice
public class GlobalDefaultExceptionHandler {
/**
* UnknownAccountException
* @param req
* @param e
* @return
*/
@ExceptionHandler(UnauthorizedException.class)
@ResponseBody
public String defaultExceptionHandler(HttpServletRequest req, Exception e)
{
return "对不起,服务器繁忙...";
}
}