shiro
shiro框架能做什么
- 认证:验证用户身份
- 授权:对用户执行访问控制:判断用户是否被允许做某事
- 会话管理:在任何环境下使用Session API,即使没有web
- 加密:以前简洁 易用的方式使用加密功能,保护或隐藏数据防止被偷窥
- Realms:聚集一个或者多个用户安全数据的数据源
- 单点登录
shiro的四大核心部分
- Authentication:身份验证
- Authorization:授权,指访问过程中决定是否有权限去访问受保护的资源
- Session Management:会话管理,管理用户特定的会话
- Cryptography:加密
shiro的三个核心组件
- Subject:正在与系统交互的用户,或者某个第三方服务。所有Subject实例都被绑定到SecurityManager上
- SecurityManager:Shiro架构的心脏协调内部各个安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当shiro与一个subject进行交互时,实质上是securitymanager处理所有繁重的的subject安全操作。
在ssm框架中
- pom文件有关依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
- web.xml中有关配置信息
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</filter>
- 与spring整合xml
<!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 包含shiro的配置文件 -->
<import resource="classpath:applicationContext-shiro.xml"/>
- shiro本身的xml(若按照上一步则该文件名为:applicationContext-shiro.xml)
<!-- 配置緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- 指定 ehcache 的配置文件,下面会给到 -->
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
</bean>
<!-- Realm 的配置(自己创建的类) init-methood是初始化的方法-->
<bean id="userAuthorizingRealm" class="com.ken.wms.security.realms.UserAuthorizingRealm" init-method="setCredentialMatcher"/>
<!-- 认证器 -->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/>
</property>
</bean>
<!-- 配置 Shiro 的 SecurityManager Bean. -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="authenticator" ref="authenticator"/>
<property name="realms">
<list>
<ref bean="userAuthorizingRealm"/>
</list>
</property>
</bean>
<!-- 配置 ShiroFilter bean: 该 bean 的 id 必须和 web.xml 文件中配置的 shiro filter 的 name 一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 装配 securityManager -->
<property name="securityManager" ref="securityManager"/>
<!-- 配置登陆页面 -->
<property name="loginUrl" value="/index.jsp"/>
<!-- 登陆成功后的一面 -->
<property name="successUrl" value="/shiro-success.jsp"/>
<!--用户访问无权限的链接时跳转此页面 -->
<property name="unauthorizedUrl" value="/shiro-unauthorized.jsp"/>
<property name="filters">
<util:map>
<entry key="roles" value-ref="anyOfRoles"/>
<entry key="authc" value-ref="extendFormAuthenticationFilter"/>
</util:map>
</property>
<!-- 具体配置需要拦截哪些 URL, 以及访问对应的 URL 时使用 Shiro 的什么 Filter 进行拦截. -->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>
<!-- 配置身份验证器,处理 Ajax 请求 -->
<bean id="extendFormAuthenticationFilter" class="com.ken.wms.security.filter.ExtendFormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="rememberMeParam" value="rememberMe"/>
<property name="loginUrl" value="/login"/>
</bean>
<bean id="anyOfRoles" class="com.ken.wms.security.filter.AnyOfRolesAuthorizationFilter"/>
<!-- 配置获取 URL 权限信息的 Factory -->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder"
factory-method="builderFilterChainDefinitionMap"/>
<bean id="filterChainDefinitionMapBuilder"
class="com.ken.wms.security.service.FilterChainDefinitionMapBuilder"/>
缓存配置文件:ehcache-shiro.xml
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache>
在Java中
- 创建FilterChainDefinitionMapBuilder
package com.ken.wms.security.service;
import com.ken.wms.dao.RolePermissionMapper;
import com.ken.wms.domain.RolePermissionDO;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 获取 URL 权限信息工厂类
* @author ken
* @since 2017/2/26.
*/
public class FilterChainDefinitionMapBuilder {
@Autowired
private RolePermissionMapper rolePermissionMapper;
private StringBuilder builder = new StringBuilder();
/**
* 获取授权信息
* @return 返回授权信息列表
*/
public LinkedHashMap<String, String> builderFilterChainDefinitionMap(){
LinkedHashMap<String, String> permissionMap = new LinkedHashMap<>();
// 固定的权限配置
permissionMap.put("/css/**", "anon");
permissionMap.put("/js/**", "anon");
permissionMap.put("/fonts/**", "anon");
permissionMap.put("/media/**", "anon");
permissionMap.put("/pagecomponent/**", "anon");
permissionMap.put("/login", "anon");
permissionMap.put("/account/login", "anon");
permissionMap.put("/account/checkCode/**", "anon");
// 可变化的权限配置
LinkedHashMap<String, String> permissions = getPermissionDataFromDB();
if (permissions != null){
permissionMap.putAll(permissions);
}
// 其余权限配置
permissionMap.put("/", "authc");
// permissionMap.forEach((s, s2) -> {System.out.println(s + ":" + s2);});
return permissionMap;
}
/**
* 获取配置在数据库中的 URL 权限信息
* @return 返回所有保存在数据库中的 URL 保存信息
*/
private LinkedHashMap<String, String> getPermissionDataFromDB(){
LinkedHashMap<String, String> permissionData = null;
List<RolePermissionDO> rolePermissionDOS = rolePermissionMapper.selectAll();
if (rolePermissionDOS != null){
permissionData = new LinkedHashMap<>(rolePermissionDOS.size());
String url;
String role;
String permission;
for (RolePermissionDO rolePermissionDO : rolePermissionDOS){
url = rolePermissionDO.getUrl();
role = rolePermissionDO.getRole();
// 判断该 url 是否已经存在
if (permissionData.containsKey(url)){
builder.delete(0, builder.length());
builder.append(permissionData.get(url));
builder.insert(builder.length() - 1, ",");
builder.insert(builder.length() - 1, role);
}else{
builder.delete(0, builder.length());
builder.append("authc,roles[").append(role).append("]");
}
permission = builder.toString();
// System.out.println(url + ":" + permission);
permissionData.put(url, permission);
}
}
return permissionData;
}
// /**
// * 构造角色权限
// * @param role 角色
// * @return 返回 roles[role name] 格式的字符串
// */
// private String permissionStringBuilder(String role){
// builder.delete(0, builder.length());
// return builder.append("authc,roles[").append(role).append("]").toString();
// }
}
- 创建domain
/**
* 用户账户信息(数据传输对象)
* @author ken
* @since 2017/2/26.
*/
public class UserInfoDTO {
/**
* 用户ID
*/
private Integer userID;
/**
* 用户名
*/
private String userName;
/**
* 用户密码(已加密)
*/
private String password;
/**
* 用户角色
*/
private List<String> role = new ArrayList<>();
- 创建realm
/**
* 用户的认证与授权
*
* @author ken
* @since 2017/2/26.
*/
public class UserAuthorizingRealm extends AuthorizingRealm {
/**
* 对用户进行角色授权
*
* @param principalCollection 用户信息
* @return 返回用户授权信息
*/
@SuppressWarnings("unchecked")
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 创建存放用户角色的 Set
Set<String> roles = new HashSet<>();
//获取用户角色
Object principal = principalCollection.getPrimaryPrincipal();
if (principal instanceof String) {
String userID = (String) principal;
if (StringUtils.isNumeric(userID)) {
try {
UserInfoDTO userInfo = userInfoService.getUserInfo(Integer.valueOf(userID));
if (userInfo != null) {
// 设置用户角色
roles.addAll(userInfo.getRole());
}
} catch (UserInfoServiceException e) {
// do logger
}
}
}
return new SimpleAuthorizationInfo(roles);
}
/**
* 对用户进行认证
*
* @param authenticationToken 用户凭证
* @return 返回用户的认证信息
* @throws AuthenticationException 用户认证异常信息
* Realm的认证方法,自动将token传入,比较token与数据库的数据是否匹配
* 验证逻辑是先根据用户名查询用户,
* 如果查询到的话再将查询到的用户名和密码放到SimpleAuthenticationInfo对象中,
* Shiro会自动根据用户输入的密码和查询到的密码进行匹配,如果匹配不上就会抛出异常,
* 匹配上之后就会执行doGetAuthorizationInfo()进行相应的权限验证。
*/
@SuppressWarnings("unchecked")
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
AuthenticationException {
String realmName = getName();
String credentials = "";
// 获取用户名对应的用户账户信息
try {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String principal = usernamePasswordToken.getUsername();
if (!StringUtils.isNumeric(principal))
throw new AuthenticationException();
Integer userID = Integer.valueOf(principal);
//依赖于/security.service.Interface.UserInfoService,UserInfoDTO中包含用户ID,用户名,密码,角色
//wms_user表
UserInfoDTO userInfoDTO = userInfoService.getUserInfo(userID);
if (userInfoDTO != null) {
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
// 设置部分用户信息到 Session
session.setAttribute("userID", userID);
session.setAttribute("userName", userInfoDTO.getUserName());
//获取该用户的所属仓库
List<RepositoryAdmin> repositoryAdmin = (List<RepositoryAdmin>) repositoryAdminManageService.selectByID(userInfoDTO.getUserID()).get("data");
session.setAttribute("repositoryBelong", (repositoryAdmin.isEmpty()) ? "none" : repositoryAdmin.get(0).getRepositoryBelongID());
// 结合验证码对密码进行处理
String checkCode = (String) session.getAttribute("checkCode");
String password = userInfoDTO.getPassword();
if (checkCode != null && password != null) {
checkCode = checkCode.toUpperCase();
credentials = encryptingModel.MD5(password + checkCode);
}
}
//比对账号密码
//principal前端传来userid
//credentials为数据库的密码,加chexkcode的MD5加密
//realmName为com.ken.wms.security.realms.UserAuthorizingRealm_0
return new SimpleAuthenticationInfo(principal, credentials, realmName);
} catch (UserInfoServiceException | RepositoryAdminManageServiceException | NoSuchAlgorithmException e) {
throw new AuthenticationException();
}
}
- 创建controller
/**
* 用户账户请求 Handler
*
* @author Ken
* @since 017/2/26.
*/
@Controller
@RequestMapping("/account")
public class AccountHandler {
// 获取当前的用户的 Subject,shiro
Subject currentUser = SecurityUtils.getSubject();
// 判断用户是否已经登陆
if (currentUser != null && !currentUser.isAuthenticated()) {
String id = (String) user.get(USER_ID);
String password = (String) user.get(USER_PASSWORD);
UsernamePasswordToken token = new UsernamePasswordToken(id, password);
// 执行登陆操作
try {
//会调用realms/UserAuthorizingRealm中的doGetAuthenticationInfo方法
currentUser.login(token);
// 设置登陆状态并记录
Session session = currentUser.getSession();
session.setAttribute("isAuthenticate", "true");
Integer userID_integer = (Integer) session.getAttribute("userID");
String userName = (String) session.getAttribute("userName");
String accessIP = session.getHost();
systemLogService.insertAccessRecord(userID_integer, userName, accessIP, SystemLogService.ACCESS_TYPE_LOGIN);
result = Response.RESPONSE_RESULT_SUCCESS;
} catch (UnknownAccountException e) {
errorMsg = "unknownAccount";
} catch (IncorrectCredentialsException e) {
errorMsg = "incorrectCredentials";
} catch (AuthenticationException e) {
errorMsg = "authenticationError";
} catch (SystemLogServiceException e) {
errorMsg = "ServerError";
}
} else {
errorMsg = "already login";
}
认证流程
- 如果没有创建subject,创建一个subject
- 调用Subject.login(token)进行登录,其自动委托给Security Manager,调用之前必须通过SecurityUtils.setSecurityManager()设置
- SecurityManager负责真正的身份验证逻辑,它会委托给Authenticator进行身份验证
- Authenticator才是真正的身份验证者,shiro API中核心身份认证入口点,此处可以自定义插入自己的实现
- Authenticator 可能会委托给相应的 Authentication Strategy 进行多 Realm身份验证,默认 MudularRealmAuthenticator 会调用 AuthenticationStrategy进行多Realm身份验证
- Authenticator 会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回抛出异常表示身份验证失败了,此处可以配置多个Realm,按照相应顺序及策略进行访问。