Shiro学习笔记
简介
Shiro是强大、精简的Java安全框架。
在认证、授权、加密和会话管理等方面提供直观而又全面的解决方案。

解释一下图中词汇:
- Authentication:认证,简单理解为登录,证明自己是谁。
- Authorization:授权,即谁是否有进行某操作的权限。
- Session Management:用户会话管理,它是环境无关的,在任何应用都可以。
- Cryptography:使用加密算法对数据进行加密,方法非常简便。
- Web Support:对web应用提供非常简便的API。
- Caching:缓存保证安全操作快速有效。
- Concurrency:Shiro支持对多线程应用。
- Testing:即使做了安全设置也不妨碍进行单元或者集成测试。
- “Run As”:权限伪装。
- Remember Me:登录后记住权限。
filterChainDefinitions定义的解决方案
在Spring Context中定义shiroFilter(org.apache.shiro.spring.web.ShiroFilterFactoryBean)时需要为其filterChainDefinitions property赋值,譬如:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login/in"/> <property name="unauthorizedUrl" value="/templates/error/unauthorized.html"/> <property name="filterChainDefinitions"> <value> /login/out = logout /login/in = authc /supplier/** = authc, roles[supplier] /dealer/** = authc, roles[dealer] </value> </property> </bean>
有时会将每一个URL作为一个授权单位进行控制,但是URL的数量让人头疼,所以我要把这些URL放在数据库进行管理,可以不重启调整授权控制。我要把他们统统从数据库Query出来放到filterChainDefinitions里。
@Service("appChainDefinitionSection")
public class AppChainDefinitionSection implements FactoryBean<Ini.Section> {
@Autowired
private AppAuthcService appAuthcService ;
@Override
public Section getObject() throws Exception {
//通过服务从数据库拉取APP应用的所有URL权限配置信息
List<FilterChainResource> resourceVOList = appAuthcService.getAppFilterChainResource();
Ini ini = new Ini();
Ini.Section section = ini.addSection(Ini.DEFAULT_SECTION_NAME);
for (FilterChainResource resourceVO : resourceVOList) {
if (!StringUtils.isEmpty(resourceVO.getUrl())
&& !StringUtils.isEmpty(resourceVO.getPermission())) {
section.put(resourceVO.getUrl(), resourceVO.getPermission());
}
}
return section;
}
@Override
public Class<?> getObjectType() {
return Ini.Section.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
public class FilterChainResource implements Serializable { private static final long serialVersionUID = 1L; //(primary = true, desc = "主键", autoAllocateId = true) private long id; //(desc = "访问路径定义", type = "VARCHAR(64)") private String url; //(desc = "访问权限定义", type = "VARCHAR(64)") private String permission; //(desc = "权限系统类别,0:CMS, 1:PIS, 2:MAINSITE, 3:MOBILE") private AuthzCategory category; @AnnonOfField(desc = "排序顺序") private int orderBy; .........................................

}
shiroSecurityFilter的介绍
拦截需要被拦截的URL,并进行create token并执行登录。
@ImportResource(value = "classpath:config/shiro-config.xml") public class BaseSecurityConfig { @Autowired protected Environment env; @Autowired protected ApplicationContext context; @Autowired private FactoryBean<Ini.Section> appChainDefinitionSection; @Bean //注册shiro的过滤器 protected FilterRegistrationBean shiroSecurityFilterRegistration() throws Exception { try { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter((Filter) shiroSecurityFilter().getObject()); registrationBean.setOrder(1); return registrationBean; } catch (Exception e) { LOGGER.error("Failed to register shiro security filter!"); throw e; } } private ShiroFilterFactoryBean shiroSecurityFilter() { ShiroFilterFactoryBean shiroSecurityFilter = new ShiroFilterFactoryBean(); shiroSecurityFilter.setLoginUrl(env.getProperty(PROPERTY_WEB_LOGIN_URL)); shiroSecurityFilter.setUnauthorizedUrl(env.getProperty(PROPERTY_WEB_UNAUTHORIZED_URL)); shiroSecurityFilter.setSecurityManager(context.getBean(BEAN_NAME_SECURITY_MANAGER,DefaultWebSecurityManager.class)); shiroSecurityFilter.setFilters(buildFilters()); try { shiroSecurityFilter.setFilterChainDefinitionMap(appChainDefinitionSection.getObject()); } catch (Throwable e) { LOGGER.error("Failed to load application chain definition!"); } return shiroSecurityFilter; } private Map<String, Filter> buildFilters() { Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>(); filterMap.put("authProduct", authProductFilter()); filterMap.put("authOrder", authOrderFilter()); return filterMap; } private Filter authProductFilter() { ProductSecurityFilter filter = new ProductSecurityFilter(); .................................................... .................................................... .................................................... return filter; } private Filter authOrderFilter() { OrderSecurityFilter filter = new OrderSecurityFilter(); .................................................... .................................................... .................................................... return filter; } }
public abstract class BaseSecurityFilter extends AccessControlFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { // 返回false由 #onAccessDenied()来继续处理 return isLoginRequest(request, response) || isLoggedIn(request, response); } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { boolean loggedIn = false; if (isLoginAttempt(request, response)) { loggedIn = executeLogin(request, response); } //登录失败了就重定向到登录页 if (!loggedIn) { redirectToLogin(request, response); } return loggedIn; } protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response); protected abstract boolean isLoggedIn(ServletRequest request, ServletResponse response); protected abstract boolean isLoginAttempt(ServletRequest request, ServletResponse response); protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { // 1.获取用户访问凭证 AuthenticationToken token = createToken(request, response); if (token == null) return false; // 2.提交凭证 try { Subject subject = getSubject(request, response); subject.login(token); return true; } catch (AuthenticationException ae) { // 当捕获的异常类型为未授权账户或已冻结时,需跳转到对应的错误页面,因此继续向上抛出。 if (ae instanceof UnknownAccountException || ae instanceof LockedAccountException) { throw ae; } return false; } } }
这些仅仅是定义了哪些URL需要认证哪些URL不要认证。我们需要在用户访问这些URL的时候去验证一下用户(角色控制)是否具备当前URL权限。
AuthorizingRealm的使用
当filter执行subject.login(token);时会调用AuthorizingRealm的doGetAuthenticationInfo执行真正的数据库数据和token数据的配对。
public class CustomAuthorizingRealm extends AuthorizingRealm { @Autowired private AuthUserService authUserService; // 认证回调函数,登录时调用. @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { AuthenticationToken token = authcToken; String userName = token.getUserName(); // 1.用户名为空,未通过URS认证 if (!StringUtils.hasText(userName)) throw new UnknownAccountException("userName is null"); // 2.根据用户名获取用户信息,取不到则创建 SimpleUser simpleUser = authUserService.findUser(userName,token.getPassword()); if (simpleUser == null) throw new UnknownAccountException(userName + " Not Found"); if (simpleUser.isLocked()) throw new LockedAccountException("The account for username " + userName + " is locked. Please contact your administrator to unlock it."); // 3.记录用户当前访问的详细信息 AccessUser neAccessUser = new AccessUser(); neAccessUser.setVistorId(simpleUser.getId()); neAccessUser.setVistorName(simpleUser.getUserName()); neAccessUser.setNickName(simpleUser.getNickName()); neAccessUser.setLock(simpleUser.isLocked()); return new SimpleAuthenticationInfo(neAccessUser, token.getCredentials(), getName()); } // 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用. @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 1.根据用户名获取用户角色和权限信息 AccessUser neAccessUser = principals.getPrimaryPrincipal(); String userName = neAccessUser.getVistorName(); List<SimpleRole> list = authUserService.getRoles(userName); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); if (list != null && !list.isEmpty()) { for (DistributionSimpleRole role : list) { // 基于Role的权限信息teacher,student,boss info.addRole(role.getName()); // 基于Permission的权限信息 info.addStringPermissions(role.getPermissionList());
} } return info; } }
Session的管理
public class CustomSessionDAO extends AbstractSessionDAO { //自行选择一个分布式存储方式 @Autowired RedisTemplate<Serializable, Session> template; @Override protected Serializable doCreate(Session session) { Serializable sessionId = generateSessionId(session); assignSessionId(session, sessionId); template.opsForValue().set(sessionId, session, 30, TimeUnit.MINUTES); return sessionId; } @Override protected Session doReadSession(Serializable sessionId) { Session session = null; if (sessionId != null) { session = template.opsForValue().get(sessionId); if (session != null) { template.expire(sessionId, 30, TimeUnit.MINUTES); } } return session; } @Override public void update(Session session) throws UnknownSessionException { if (session == null) { return; } template.opsForValue().set(session.getId(), session, 30, TimeUnit.MINUTES); } @Override public void delete(Session session) { System.out.println("删除session:" + session.getHost()); } @Override public Collection<Session> getActiveSessions() { return null; } }
与spring的集成
<?xml version="1.0" encoding="UTF-8"?> <beans> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <!--自定义session管理--> <bean id="customeSessionDAO" class="com...CustomSessionDAO"></bean> <bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!--session会话默认闲置半小时失效--> <property name="sessionDAO" ref="customeSessionDAO"/> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customAuthorizingRealm"/> <property name="sessionManager" ref="defaultWebSessionManager"/> </bean> <!-- Spring Shiro AOP --> <aop:config proxy-target-class="true" /> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="org.apache.shiro.authz.UnauthenticatedException">error/403</prop> <prop key="org.apache.shiro.authz.UnauthorizedException">error/403</prop> <prop key="org.apache.shiro.authc.LockedAccountException">error/locked</prop> <!-- <prop key="java.lang.Throwable">error/500</prop> --> </props> </property> </bean> </beans> ***.properties web.loginUrl=/login web.unauthorizedUrl=/health filter.chain.definitions=/s/** = anon\n/health/** = anon\n/logout = anon\n/login/** = anon\n/ext/** = anon\n/dealer/** = authMainsiteDealer\n/supplier/** = authMainsiteSupplier
controller与permission的配合
- @RequiresAuthentication:访问或者调用被注解的类或者方法时通过认证。
- @RequiresGuest:需要从未通过认证且没有被记住(Remember me)。
- @RequiresPremissions:需要特定的权限。
- @RequiresRoles:需要特定的角色。
- @RequiresUser:需要已通过认证



浙公网安备 33010602011771号