Java 权限框架 Shiro 实战二:与spring集成、filter机制
转自:https://www.cnblogs.com/digdeep/archive/2015/07/04/4620471.html
Shiro和Spring的集成,涉及到很多相关的配置,涉及到shiro的filer机制以及它拥有的各种默认filter,涉及到shiro的权限判断标签,权限注解,涉及到session管理等等方面。
1. 配置
首先需要在web.xml中专门负责接入shiro的filter:
<!-- shiro 安全过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
并且需要放在所有filter中靠前的位置,比如需要放在siteMesh的过滤器之前。
DelegatingFilterProxy 表示这是一个代理filter,它会将实际的工作,交给spring配置文件中 id="shiroFilter" 的bean来处理:
public class DelegatingFilterProxy extends GenericFilterBean {
private String contextAttribute;
private WebApplicationContext webApplicationContext;
private String targetBeanName;
private boolean targetFilterLifecycle = false;
private volatile Filter delegate;
private final Object delegateMonitor = new Object();
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
public abstract class GenericFilterBean implements
Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
}
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
try {
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
// Let subclasses do whatever initialization they like.
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
}
}
// Let subclasses do whatever initialization they like.
initFilterBean();
Filter 接口的 init 方法调用 initFilterBean(), 而该方法在子类中进行实现,它先获得 this.targetBeanName = getFilterName(); bean的名称,也就是id,然后对其进行初始化:this.delegate = initDelegate(wac); 其实就是从bean工厂中根据bean的名称找到bean.
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
而 shiroFilter在spring中的配置如下:
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/"/>
<property name="unauthorizedUrl" value="/unauthorized"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="passThruAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/reg/** = anon <!-- 注册相关 -->
/login = authc
/logout = logout
/authenticated = authc
/loginController = anon
/js/** = anon
/css/** = anon
/img/** = anon
/html/** = anon
/font-awesome/** = anon
<!-- /** = anon
/user/modifyPassword = perms["user:update", "user:select"]
-->
/** = user
</value>
</property>
</bean>
上面的shiroFilter的配置又引出了 securityManager 和 shiro 的filter机制和他自带的一些filter.
2. securityManager 级相关配置
在上一篇文章 Java 权限框架 Shiro 实战一:理论基础 中我们知道securityManager是shiro的顶层对象,它管理和调用其它所有子系统,负责系统的安全。我们知道shiro有两个类型的securityManager:一个是JavaSE环境,默认是DefaultSecurityManager;一个是web环境,默认是DefaultWebSecurityManager。所以我们web环境肯定应该使用后者。我们从顶层对象一层一层向下配置。先看securityManager如何配置:
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
上面的配置相当于调用SecurityUtils.setSecurityManager(securityManager) ,来注入了下面配置的 securityManager(DefaultWebSecurityManager) :
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
它默认使用的session管理器是 ServletContainerSessionManager,所以上面没有配置,所以就使用默认值。配置了就会覆盖下面的默认值:
public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}
显然 securityManager 最重要的工作就是用户登录认证和获得用户的权限等相关信息,所以 realm 是其最重要的依赖:
<!-- Realm实现 -->
<bean id="userRealm" class="com.ems.shiro.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="false"/>
</bean>
要理解上面userRealm的配置,就的先理解 UserRealm 的继承体系:
UserRealm 继承 AuthorizingRealm 显然是为了获取权限信息,对用户进行访问控制;继承AuthenticatingRealm显然是为了获得用户的认证信息,对用户进行认证。而 credentialsMatcher 就是 AuthenticatingRealm 使用来进行密码验证的依赖的组件:
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {/**
* Credentials matcher used to determine if the provided credentials match the credentials stored in the data store.
*/
private CredentialsMatcher credentialsMatcher;
再看其credentialsMatcher bean的配置:
<!-- 凭证匹配器(验证登录密码是否正确) -->
<bean id="credentialsMatcher" class="com.ems.shiro.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"/>
<property name="hashAlgorithmName" value="SHA-256"/>
<property name="hashIterations" value="2"/>
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
配置就是 hash加密的相关参数:hash算法,hash迭代次数等。到这里 shiro 登录验证的配置就完了。至于获取用户信息和用户的权限的信息,都在userRealm中实现了:
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String)principals.getPrimaryPrincipal();
User user = userService.getUserByUserName (userName );
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRolesByUserId(user.getId()));
authorizationInfo.setStringPermissions(userService.findPermissionsByUserId(user.getId()));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String)token.getPrincipal();
User user = userService.getUserByUserName(userName);
if(user == null) {
throw new UnknownAccountException();//没找到账户
}
if(user.getLocked() == 0) {
throw new LockedAccountException(); //帐号锁定
}
if(user.getLocked() == 2){
throw new AuthenticationException("account was inactive");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(),
user.getPassword(), // 密码
ByteSource.Util.bytes(user.getCredentialsSalt()), // salt
getName() // realm name
);
return authenticationInfo;
}
securityManager会在需要的时候回调上面 的 doGetAuthorizationInfo 和 doGetAuthenticationInfo 方法,从realm中获得登录认证信息和用户权限信息。至于 rememberMeManager 主要是实现使用cookie表示我已经登录过了,下次不需要重新登录,这一个功能,也就是“记住我”登录过这一功能:
<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
<property name="cipherKey"
value="#{T(org.apache.shiro.codec.Base64).decode('9FvVhtFLUs0KnA3Kprsdyg==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>
还有cacheManager的配置:
<!--ehcache-->
<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache/ehcache.xml"/>
</bean>
<bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcacheManager"/>
</bean>
<!-- 缓存管理器 -->
<bean id="cacheManager" class="com.ems.shiro.SpringCacheManagerWrapper">
<property name="cacheManager" ref="springCacheManager"/>
</bean>
使用的是 EhCache.
3. Shiro 的filter机制和自带的filter
Shiro的filter是基于Servlet的Filter接口实现的。我们通过Shiro提供的form登录filter:FormAuthenticationFilter 和 ShiroFilter 看看其实现:
继承中的每一层都实现了一些功能:
1> NameableFilter:实现给filter取名的功能(Allows a filter to be named via JavaBeans-compatible)
/**
* Allows a filter to be named via JavaBeans-compatible*/
public abstract class NameableFilter extends AbstractFilter implements Nameable {
/**
* The name of this filter, unique within an application.
*/
private String name;
2> OncePerRequestFilter : 保证对于同一个request,fiter只执行一次(Filter base class that guarantees to be just executed once per request)
/**
* Filter base class that guarantees to be just executed once per request,
* on any servlet container. It provides a {@link #doFilterInternal}
* method with HttpServletRequest and HttpServletResponse arguments.*/
public abstract class OncePerRequestFilter extends NameableFilter {
3> AdviceFilter: SpringMVC风格的过滤器(就是preHandle, postHandle,afterCompletion 三接口的过滤器)
/**
* A Servlet Filter that enables AOP-style "around" advice for a ServletRequest via
* preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse),
* postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse),
* and afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception)hooks.
*/
public abstract class AdviceFilter extends OncePerRequestFilter {
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
return true;
}
@SuppressWarnings({"UnusedDeclaration"})
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
}
@SuppressWarnings({"UnusedDeclaration"})
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
}
4> PathMatchingFilter:该过滤器仅仅处理指定的路径(比如上面的配置:/js/** = anon,表示对 /js/ 目录和其子目录的请求,交给anon过滤器处理)
/**
* <p>Base class for Filters that will process only specified paths and allow all others to pass through.</p>*/
public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {
5> AccessControlFilter: 实现提供对资源的访问控制,没有权限时,重定向到登录页面,登录之后跳转到原来的那个页面
/**
* Superclass for any filter that controls access to a resource and may redirect the user to the login page
* if they are not authenticated. This superclass provides the method
* saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
* which is used by many subclasses as the behavior when a user is unauthenticated.*/
public abstract class AccessControlFilter extends PathMatchingFilter {
6> AuthenticationFilter: 实现对访问用户的认证要求,也就是必须登录了才能访问
/**
* Base class for all Filters that require the current user to be authenticated. This class encapsulates the
* logic of checking whether a user is already authenticated in the system while subclasses are required to perform
* specific logic for unauthenticated requests.*/
public abstract class AuthenticationFilter extends AccessControlFilter {
7> AuthenticatingFilter: 实现判断用户是否有权限访问某资源。
/**
* An AuthenticationFilter that is capable of automatically performing an authentication attempt
* based on the incoming request.*/
public abstract class AuthenticatingFilter extends AuthenticationFilter {
8> FormAuthenticationFilter:shiro提供的用于实现用户登录功能,如果我们打算自己实现登录,那么我们应用 PassThruAuthenticationFilter 来替代
/**
* Requires the requesting user to be authenticated for the request to continue, and if they are not, forces the user
* to login via by redirecting them to the setLoginUrl(String) you configure.
* If you would prefer to handle the authentication validation and login in your own code, consider using the
* PassThruAuthenticationFilter instead, which allows requests to the loginUrl to pass through to your application's code directly.*/
public class FormAuthenticationFilter extends AuthenticatingFilter {
9> PassThruAuthenticationFilter : 用于我们自己在controller中实现登录逻辑时替代FormAuthenticationFilter
/**
* An authentication filter that redirects the user to the login page when they are trying to access
* a protected resource. However, if the user is trying to access the login page, the filter lets
* the request pass through to the application code.
* The difference between this filter and the FormAuthenticationFilter is that
* on a login submission (by default an HTTP POST to the login URL), the FormAuthenticationFilter filter
* attempts to automatically authenticate the user by passing the username and password request parameter values to
* Subject.login(AuthenticationToken) directly.
* Conversely, this controller always passes all requests to the loginUrl through, both GETs and POSTs.
* This is useful in cases where the developer wants to write their own login behavior, which should include a
* call to Subject.login(AuthenticationToken) at some point. For example, if the developer has their own custom MVC
* login controller or validator, this PassThruAuthenticationFilter may be appropriate.*/
public class PassThruAuthenticationFilter extends AuthenticationFilter {
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
return true;
} else {
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
}
10> Shiro 自带的filter:
Shiro自身提供了很多的默认filter 来供我们使用,主要分为两种:一是 登录认证相关的filter;一是权限访问控制相关的filter;
登录认证相关的filter有:
1)filter名称: anon, 实现类org.apache.shiro.web.filter.authc.AnonymousFilter,主要用于静态资源的访问,表示无需登录就可以访问;
2)filter名称: authc, 实现类org.apache.shiro.web.filter.authc.FormAuthenticationFilter,主要用于表单登录,没有登录则跳转登录url;
3)filter名称: user, 实现类org.apache.shiro.web.filter.authc.UserFilter,主要用于要求用户已经登录或者通过“记住我”功能登录了也行。
4)filter名称: logout, 实现类org.apache.shiro.web.filter.authc.LogoutFilter,主要用于用户登出
5)filter名称: authcBasic,authc的简化形式,略。
权限访问控制相关的filter有:
1)filter名称: roles, 实现类org.apache.shiro.web.filter.authc.RolesAuthorizationFilter,主要用于验证用户必须拥有某角色,才能继续访问;
2)filter名称: perms, 实现类org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter,主要用于验证用户必须拥有某权限,才能继续访问;
3)filter名称: ssl, 实现类org.apache.shiro.web.filter.authc.SslFilter,主要用于要求访问协议是https才能访问,不然跳转到https的443短裤;
4)filter名称: port rest noSessionCreation,略。
我们上面的shiroFilter的配置中,已经使用过了上面这些自带的filter:
/reg/** = anon <!-- 注册相关 -->
/login = authc
/logout = logout
/authenticated = authc
/loginController = anon
/js/** = anon
/css/** = anon
/img/** = anon
/html/** = anon
/font-awesome/** = anon
/** = user
我们看到 /reg/** 注册相关的,/js/**静态资源都是使用的 anon匿名过滤器,不要求用户已经登录就可以访问。
/** = user 放在最后是要求除了上面那些 url 之外的访问路径,都需要登录认证过或者通过记住我登录认证过。因为路径比较是从上面开始列出来的先开始比较的,匹配了就走该过滤器,不会继续下面的过滤器了。
4. shiro的权限标签
Shiro提供了相应的权限标签,用来实现根据用户的角色和权限来显示它相应的菜单和按钮。首先需要导入shiro标签库:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
标签库的定义位于:shiro-web.jar 包中的META-INF/shiro.tld文件中:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.1.2</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>Apache Shiro</short-name>
<uri>http://shiro.apache.org/tags</uri>
<description>Apache Shiro JSP Tag Library.</description>
<tag>
<name>hasPermission</name>
<tag-class>org.apache.shiro.web.tags.HasPermissionTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current Subject (user)
'has' (implies) the specified permission (i.e the user has the specified ability).
</description>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>lacksPermission</name>
<tag-class>org.apache.shiro.web.tags.LacksPermissionTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current Subject (user) does
NOT have (not imply) the specified permission (i.e. the user lacks the specified ability)
</description>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>hasRole</name>
<tag-class>org.apache.shiro.web.tags.HasRoleTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current user has the specified role.</description>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>hasAnyRoles</name>
<tag-class>org.apache.shiro.web.tags.HasAnyRolesTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current user has one of the specified roles from a
comma-separated list of role names.
</description>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>lacksRole</name>
<tag-class>org.apache.shiro.web.tags.LacksRoleTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current user does NOT have the specified role
(i.e. they explicitly lack the specified role)
</description>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>authenticated</name>
<tag-class>org.apache.shiro.web.tags.AuthenticatedTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current user has successfully authenticated
_during their current session_. It is more restrictive than the 'user' tag.
It is logically opposite to the 'notAuthenticated' tag.
</description>
</tag>
<tag>
<name>notAuthenticated</name>
<tag-class>org.apache.shiro.web.tags.NotAuthenticatedTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current user has NOT succesfully authenticated
_during their current session_. It is logically opposite to the 'authenticated' tag.
</description>
</tag>
<tag>
<name>user</name>
<tag-class>org.apache.shiro.web.tags.UserTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current Subject has a known identity, either
from a previous login or from 'RememberMe' services. Note that this is semantically different
from the 'authenticated' tag, which is more restrictive. It is logically
opposite to the 'guest' tag.
</description>
</tag>
<tag>
<name>guest</name>
<tag-class>org.apache.shiro.web.tags.GuestTag</tag-class>
<body-content>JSP</body-content>
<description>Displays body content only if the current Subject IS NOT known to the system, either
because they have not logged in or they have no corresponding 'RememberMe' identity. It is logically
opposite to the 'user' tag.
</description>
</tag>
<tag>
<name>principal</name>
<tag-class>org.apache.shiro.web.tags.PrincipalTag</tag-class>
<body-content>JSP</body-content>
<description>Displays the user's principal or a property of the user's principal.</description>
<attribute>
<name>type</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>property</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>defaultValue</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
其中最重要的标签是关于角色和权限的:
<shiro:hasAnyRoles name="student,teacher"></shiro:hasAnyRoles>
<shiro:hasPermission name="user:delete"></shiro:hashPermission>
其它还有关于登录与否的标签:
<shiro:guest></shiro:guest> 未登录可以显示的信息;
<shiro:user></shiro:user> 用户已经登录或者通过记住我登录后显示的信息;
<shiro:authenticated></shiro:authenticated> 必须是实际登录,不是通过记住我登录的
其它标签参考 shiro.tld文件。
shiro标签使用示例:
<!--sidebar-menu-->
<div id="sidebar"><a href="javascript:;" class="visible-phone"><i class="icon icon-home"></i> Dashboard</a>
<ul>
<shiro:hasAnyRoles name="student,teacher">
<li id="li_queryScore"><a href="${ctx}/user/queryScore"><i class="icon icon-home"></i><span>查询成绩</span></a></li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="teacher,admin">
<li id="li_showStudentInfo"><a href="${ctx}/student/showStudentInfo"><i class="icon icon-home"></i><span>查询学生信息</span></a></li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="admin">
<li id="li_showTeacherInfo"><a href="${ctx}/teacher/showTeacherInfo"><i class="icon icon-home"></i><span>查询教师信息</span></a></li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="admin">
<li id="li_getStatistic"><a href="${ctx}/statistics/getStatistic"><i class="icon icon-th"></i><span>统计</span></a></li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="student,teacher,admin">
<li id="li_password"><a href="${ctx}/user/password"><i class="icon icon-inbox"></i><span>密码修改</span></a></li>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
<li id="li_showPrivilege"><a href="${ctx}/priv/showPrivilege"><i class="icon icon-fullscreen"></i><span>权限设置</span></a></li>
</shiro:hasRole>
<shiro:hasAnyRoles name="teacher">
<li id="li_scoreRatio"><a href="${ctx}/set/scoreRatio"><i class="icon icon-tint"></i><span>成绩比例设置</span></a></li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="admin">
<li id="li_getSetting"><a href="${ctx}/set/getSetting"><i class="icon icon-tint"></i><span>成绩录入时间设置</span></a></li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="student,teacher">
<li id="li_queryReExam"><a href="${ctx}/user/queryReExam"><i class="icon icon-pencil"></i><span>补考名单</span></a></li>
<li id="li_queryReLearn"><a href="${ctx}/user/queryReLearn"><i class="icon icon-pencil"></i><span>重修名单</span></a></li>
</shiro:hasAnyRoles>
</ul>
</div>
<!--sidebar-menu-->
效果是根据用户拥有的角色,来显示左侧有哪些菜单项。
5. shiro 权限注解的使用
shiro对权限的控制,除了前面给出的在 shiroFilter这个bean中配置的过滤器:
<property name="filterChainDefinitions">
<value>
/reg/** = anon <!-- 注册相关 -->
/login = authc
/logout = logout
/loginController = anon
/js/** = anon
/css/** = anon
/img/** = anon
/html/** = anon
/font-awesome/** = anon
/** = user
</value>
</property>
之外,最重要的就是使用注解的方式来进行访问控制的实现了。shiro权限注解可以达到方法级别的细腻控制,可以控制具有某些权限或者某些角色的用户才能访问某个方法(某个url)。先要开启shiro权限注解功能,开启方法参见文档:http://shiro.apache.org/spring.html
Here is how to enable these annotations. Just add these two bean definitions to applicationContext.xml:
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<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权限注解的方法二:
<aop:config />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<aop:config /> 表示开启spring注解,而 DefaultAdvisorAutoProxyCreator 表示会自动创建代理。但是二者最好不要同时使用。
AuthorizationAttributeSourceAdvisor 通过其依赖的 securityManager 来获取用户的角色和权限信息,进而可以进行权限判断。
支持的shiro注解有:
@SuppressWarnings({"unchecked"})
public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
};
protected SecurityManager securityManager = null;
public AuthorizationAttributeSourceAdvisor() {
setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}
RequiresPermissions, RequiresRoles, RequiresUser, RequiresGuest, RequiresAuthentication
主要是通过: AopAllianceAnnotationsAuthorizingMethodInterceptor 类来实现的:
public class AopAllianceAnnotationsAuthorizingMethodInterceptor extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor{
public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
List<AuthorizingAnnotationMethodInterceptor> interceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
//use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the
//raw JDK resolution process.
AnnotationResolver resolver = new SpringAnnotationResolver();
//we can re-use the same resolver instance - it does not retain state:
interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
interceptors.add(new UserAnnotationMethodInterceptor(resolver));
interceptors.add(new GuestAnnotationMethodInterceptor(resolver)); setMethodInterceptors(interceptors);
}
上面注入了注解的拦截器实现。具体的拦截判断权限过程实现如下:
public class RoleAnnotationHandler extends AuthorizingAnnotationHandler {
public RoleAnnotationHandler() {
super(RequiresRoles.class);
}
public void assertAuthorized(Annotation a) throws AuthorizationException {
if (!(a instanceof RequiresRoles)) return;
RequiresRoles rrAnnotation = (RequiresRoles) a;
String[] roles = rrAnnotation.value();
if (roles.length == 1) {
getSubject().checkRole(roles[0]);
return;
}
if (Logical.AND.equals(rrAnnotation.logical())) {
getSubject().checkRoles(Arrays.asList(roles));
return;
}
if (Logical.OR.equals(rrAnnotation.logical())) {
// Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
boolean hasAtLeastOneRole = false;
for (String role : roles) if (getSubject().hasRole(role)) hasAtLeastOneRole = true;
// Cause the exception if none of the role match, note that the exception message will be a bit misleading
if (!hasAtLeastOneRole) getSubject().checkRole(roles[0]);
}
}
}
主要是上面的方法 assertAuthorized(Annotation a) 中来实现对用户是否拥有某些角色进行判断的。其实还是很简单的。
shiro权限注解使用方法如下所示:
@RequiresPermissions(value={"user:update", "user:select"}, logical= Logical.AND)
@RequestMapping(value="/modifyPassword", method=RequestMethod.POST)
@ResponseBody
public Map<String, String> modifyPassword(String oldPassword, String newPassword, HttpSession session) {
Map<String, String> map = new HashMap<>();
if(oldPassword == null || newPassword == null || newPassword.length() < 8 || newPassword.length() > 32){
map.put("result", "error");
map.put("msg", "密码必须在8到20位之间");
return map;
}
User user = (User)session.getAttribute(ConstantConfig.LONGIN_USER);
if(user != null){
PasswordHelper ph = new PasswordHelper();
if(!ph.checkPasswordAndEncryptPassword(oldPassword, user)){ // 判断输入的 oldPassword是否正确
map.put("result", "error");
map.put("msg", "密码错误");
return map;
}else{
user.setPassword(newPassword);
ph.encryptPassword(user);
int result = this.userService.updateUserById(user);
if(result > 0){
map.put("result", "ok");
map.put("msg", "密码修改成功,请重新登录");
}else{
map.put("result", "error");
map.put("msg", "密码修改失败");
}
return map;
}
}
return map;
}
@RequiresPermissions(value={"user:update", "user:select"}, logical= Logical.AND)
表示必须有 对 user 表的同时拥有 查询和更新权限,才能修改密码。
Java 权限框架 Shiro 实战二:与spring集成、filter机制的更多相关文章
- Java 权限框架 Shiro 实战一:理论基础
Apache Shiro 官网地址:http://shiro.apache.org/ Apache Shiro is a powerful and easy-to-use Java security ...
- 五大 JAVA Web 框架的优缺点对比,Spring MVC 领先
毫无疑问,Java 是当今世界上最重要的编程语言之一.js 框架给程序员提供了 一个可以构建程序的坚实基础.它包括定义的类和功能,用于硬件设备管理,与系统软件交互并处理输入,让开发人员变得更轻松.Ja ...
- 1.java安全框架SHIRO
1. shiro介绍 Apache Shiro是一个强大且易用的java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以快速.轻松地获得任何应用程序,从最小的移 ...
- 权限框架 - shiro 授权demo
之前说了权限认证,其实也就是登录验证身份 这次来说说shiro的授权 shiro可以针对角色授权,或者访问资源授权 两者都行,但是在如今的复杂系统中,当然使用后者,如果你是小系统或者私活的话,前者即可 ...
- 手把手实现Java权限(1)-Shiro介绍
功能介绍 Authentication :身份认证/登录.验证用户是不是拥有对应的身份: Authorization :授权,即权限验证.验证某个已认证的用户是否拥有某个权限:即推断用 户能否做事 ...
- 权限框架 - shiro 自定义realm
上篇文章中是使用的默认realm来实现的简单登录,这仅仅只是个demo,真正项目中使用肯定是需要连接数据库的 首先创建自定义realm文件,如下: 在shiro中注入自定义realm的完全限定类名: ...
- 权限框架 - shiro 简单入门实例
前面的帖子简单的介绍了基本的权限控制,可以说任何一个后台管理系统都是需要权限的 今天开始咱们来讲讲Shiro 首先引入基本的jar包 <!-- shiro --> <dependen ...
- .net通用权限框架B/S(二) 数据库设计
1.组织机构---员工是1:n关系 2.角色---员工n:n 3.角色--导航菜单n:n 4.操作权限(id)---导航菜单(prms_id)n:n [此处是n:n关系正常是生成第三张表存放多对多 ...
- java安全框架shiro(一)
第一个简单的案例 ,通过读取.ini文件的方式模拟登陆, 1.通过Factory工厂的getInstance()方法来获取SecurityManager的实例,实例化Factory需要一个ini文件的 ...
随机推荐
- 【java规则引擎】《Drools7.0.0.Final规则引擎教程》第4章 4.2 lock-on-active
转载至:https://blog.csdn.net/wo541075754/article/details/75208955 lock-on-active 当在规则上使用ruleflow-group属 ...
- 修改ThinkPHP缓存为Memcache的方法
一般来说,ThinkPHP的默认缓存方式是以File文件方式实现的,运行时会在/Runtime/Temp 下生成很多的缓存文件. 有的情况下服务器装了memcached之后,需要将ThinkPHP的缓 ...
- C# 使用ZXing.NET生成一维码、二维码
以上图片是本示例中的实际运行效果,在生活中我们的一维码(也就是条形码).二维码 使用已经非常广泛,那么如何使用c#.net来进行生成一维码(条形码).二维码呢? 使用ZXing来生成是非常方便的选择, ...
- 详解Hadoop Slots的含义
Slots是Hadoop的一个重要概念.然而在Hadoop相关论文,slots的阐述难以理解.网上关于slots的概念介绍也很少,而对于一个有经验的Hadoop开发者来说,他们可能脑子里已经理解了sl ...
- postman的Testing examples(常用方法)
在实现接口自动测试的时候,会经常遇到接口参数依赖的问题,例如调取登录接口的时候,需要先获取登录的key值,而每次请求返回的key值又是不一样的,那么这种情况下,要实现接口的自动化,就要用到postma ...
- linux ping报错Name or service not known
ubuntu设置静态ip以后忘记设置dns,ping的时候报错:Name or service not known 添加dns即可 vi /etc/resolv.conf nameserver 8.8 ...
- JSP中的EL (Express Language表达式语言)
EL语言的目的: 用于无java代码的JSP页面 创建el1.jsp,el2.jsp el1.jsp <%@ page language="java" contentType ...
- [C++ Primer] : 第13章: 拷贝控制
拷贝, 赋值与销毁 当定义一个类时, 我们显示地或隐式地指定在此类型的对象拷贝, 移动, 赋值和销毁时做什么. 一个类通过定义5种特殊的成员函数来控制这些操作, 包括: 拷贝构造函数, 拷贝赋值运算符 ...
- PHP中文件类型 文件属性 路径以及 文件相关的函数
一: 文件类型判断: 1.is_dir() 判断是不是目录 2.is_file() 判断是不是文件 3.is_executable() 判断是不是可执行文件 4.is_readable() 判断是 ...
- FileStream 和StreamWriter 一起用时
StreamWriter Flush 即可. FileStream Flush 无用.