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>/*
- 与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.insertAcce***ecord(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,按照相应顺序及策略进行访问。