Interceptor与Shiro
什么是Interceptor
依赖于web框架(在SpringMVC中就是依赖于SpringMVC框架,在Struts2中就是依赖Struts2框架)。在实现上,基于Java的反射机制或者是基于JDK实现的动态代理,属于面向切面编程(AOP)的一种运用。通俗来说,就是提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行。
Interceptor实现原理
Java的反射机制(反射的详细内容参考博文)实现了动态代理。
什么是代理和动态代理?代理模式为其它对象提供一种代理,以控制对某个对象的访问。动态代理是指客户通过代理类来调用其它对象的方法。
使用Java的反射机制创建动态代理对象,让代理对象在调用目标方法之前和之后分别做一些事情,然后动态代理对象决定是否调用以及何时来调用被代理对象的方法。这样的动态代理的应用即形成了拦截器(Interceptor)。
Interceptor在SSM中的使用
-
有两个步骤:
1.实现HandlerInterceptor
接口和方法
2.将拦截器添加到MVC中。 -
创建一个类
MyInterceptor
,实现HandlerInterceptor
接口并重写方法
public class MyInterceptor implements HandlerInterceptor {
//请求发送到Controller之前调用
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
/**
* 配置跨域
*/
httpServletResponse.setHeader("Access-Control-Allow-Origin","*");//允许所有域名访问
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, token");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
/**
* 根据自己的业务编写代买
* 以验证请求头中的token为例
*/
if(httpServletRequest.getHeader("token") == null){
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
HashMap<String,String> returnJson = new HashMap<>();
returnJson.put("status","415");
returnJson.put("message","拒绝");
returnJson.put("data",null);
PrintWriter out;
out = httpServletResponse.getWriter();
out.append(returnJson.toString());
return false;
}
String token = httpServletRequest.getHeader("token");
if(token.equals("test")){
return true;
}
return false;
}
//请求发送到Controller之后调用
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
//完成请求的处理的回调方法
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
-
将
MyInterceptor
配置到SpringMVC中
在Spring的MVC配置中加入<mvc:interceptors>
标签,控制拦截的范围以及注入拦截器<!-- 配置拦截器 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.cloneZjrt.util.interceptor.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>
什么是Shiro
Shiro是Java的一个安全框架。因为它相当简单,对比 Spring Security,即使功能上没有 Spring Security强大,但是在实际工作时,Shiro已经能满足大部分情况。
Shiro核心组件
- Subject:主体,即当前操作用户
- SecurityManager:安全管理器,它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务
- Realm:场所,这里指Shiro获取应用安全数据的方法。当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息(即我们将查询接口在Realm模块中调用,查询到的数据会成为Shiro是否认证,是否授权的依据)。
- Authenticator:认证器,AuthenticationStrategy如果存在多个realm,则按着具体的策略进行登录控制,例如:如果有一个realm成功即可登录、必须所有realm都成功才能登录等
- Authorizer:授权器,决定subject能拥有什么样角色或者权限。
- SessionManager:session管理器,创建和管理用户session。通过设置这个管理器,shiro可以在任何环境下使用session。
- CacheManager:缓存管理器,可以减少不必要的后台访问。提高应用效率,增加用户体验。
- Cryptography:Shiro的api大幅度简化java的api中繁琐的密码加密。
Shiro的主要功能
1.Authenticator(认证过程)
- Subject(主体)请求认证,调用subject.login(token)
- SecurityManager (安全管理器)执行认证
- SecurityManager通过ModularRealmAuthenticator进行认证。
- ModularRealmAuthenticator将token传给realm,realm根据token中用户信息从数据库查询用户信息(包括身份和凭证)
realm如果查询不到用户给ModularRealmAuthenticator返回null,ModularRealmAuthenticator抛出异常(用户不存在) - realm如果查询到用户给ModularRealmAuthenticator返回AuthenticationInfo(认证信息)
- ModularRealmAuthenticator拿着AuthenticationInfo(认证信息)去进行凭证(密码)比对。如果一致则认证通过,如果不致抛出异常(凭证错误)。
2.Authorizer(授权过程)
- 调用方法isPermitted("")或者hasRole(""),对subject进行授权
- SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
- ModularRealmAuthorizer执行realm(自定义的CustomRealm)从数据库查询权限数据调用realm的授权方法:doGetAuthorizationInfo
- realm从数据库查询权限数据,返回ModularRealmAuthorizer
- ModularRealmAuthorizer调用PermissionResolver进行权限串比对
- 如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则没有权限,抛出异常。
3.sessionManager:Shiro默认是从cookie中读取sessionId以此来维持会话,也可以自定义sessionManager,继承DefaultWebSessionManager类,重写getSessionId方法。如果在分布式集群环境中可以把session放在redis管理,可以实现session共享。
4.cacheManager:Shiro默认整合了EhCache,来实现缓存,可以自定义cacheManager。如果在分布式集群环境中可以把session放在redis管理,可以实现cache共享。
5.rememeberMeManager:用户登陆选择“自动登陆”本次登陆成功会向cookie写身份信息,下次登陆从cookie中取出身份信息实现自动登陆。如果使用了UserFilter(例如:/user*=user),又如果设置记住我,下次访问这些url时可以不用登陆。
6.常用注解:
@RequiresAuthentication
表示当前 Subject 已经通过 login 进行了身份验证;即 Subject. isAuthenticated()返回 true
@RequiresUser
表示当前 Subject 已经身份验证或者通过记住我登录的。
@RequiresGuest
表示当前 Subject 没有身份验证或通过记住我登录过,即是游客身份
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)
表示当前 Subject 需要角色 admin 和 user。
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)
表示当前 Subject 需要权限 user:a 或 user:b
SSM整合Shiro
- 添加依赖
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 引入ehcache的依赖,给shiro做缓存权限用的 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
web.xml
中添加过滤器(Filter
)
<!--shiro拦截器-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 配置
spring-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义shiro安全管理器,并配置需要实现的功能-->
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<!--实现realm功能-->
<property name="realm" ref="realm"/>
<!--实现cacheManager功能-->
<property name="cacheManager" ref="cacheManager"/>
<!--实现seeionManager功能-->
<property name="sessionManager" ref="sessionManager"/>
<!--实现记住我功能-->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 定义自己实现的realm域,并配置凭证匹配器-->
<bean id="realm" class="com.cloneZjrt.util.MyRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!-- 配置shiro过滤器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/index"/>
<property name="unauthorizedUrl" value="/unauth"/>
<property name="filterChainDefinitions">
<value>
<!--anon表示无需验证,authc表示需要验证-->
/login = anon
/sublogin = anon
/* = authc
</value>
</property>
</bean>
<!--配置logout登出管理,id只能为logout,并且在shiro拦截器中需要定义lgout-->
<bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<property name="redirectUrl" value="/login"/>
</bean>
<!--实现cacha缓存,读取ehcache配置文件-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:config/ehcache.xml"/>
</bean>
<!--配置session管理器-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="300000"/>
<property name="deleteInvalidSessions" value="true"/>
</bean>
<!--设置记住我-->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="remeberMeCookies"/>
</bean>
<!-- cookis配置-->
<bean id="remeberMeCookies" class="org.apache.shiro.web.servlet.SimpleCookie">
<!--设置最大存活时间和cookie名称-->
<property name="maxAge" value="604800"/>
<property name="name" value="remeberMe"/>
</bean>
<!--开启shiro权限注解功能,并配置securityManager属性-->
<aop:config proxy-target-class="true"></aop:config>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--定义凭证匹配器,也就是对密码进行算法加密和次数-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"/>
</bean>
<!--配置ahtuc过滤器(表单域名称),在页面中账号和密码的name属性的值必须和下面定义的相同-->
<bean id ="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="rememberMeParam" value="remeberMe"/>
</bean>
<!--异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings" >
<props>
<!--认证异常和授权异常 -->
<prop key="org.apache.shiro.authz.UnauthenticatedException">login</prop>
<prop key="org.apache.shiro.authz.UnauthorizedException">refuse</prop>
</props>
</property>
</bean>
</beans>
ApplicationContext.xml
中加入spring-shiro.xml
<import resource="classpath*:/spring/spring-shiro.xml"/>
- 新建一个类
MyRealm
继承AuthorizingRealm
,并实现其中的doGetAuthorizationInfo
和doGetAuthenticationInfo
方法
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 获取当前用户的密码
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String name = (String) authenticationToken.getPrincipal();
String password = getPassword(name);
if (password == null) {
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, password, "MyRealm");
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(name));
return simpleAuthenticationInfo;
}
private String getPassword(String name) {
String password = userService.getPasswordByName(name);
return password;
}
/**
* 获取当前用户的权限集
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String name = (String) principalCollection.getPrimaryPrincipal();
Set<String> roles = getRoleByName(name);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
private Set<String> getRoleByName(String name) {
Set<String> set = userService.getRoles(name);
return set;
}
}
- Controller层模拟功能
@PostMapping(value = "/login")
public String login(UserEntity user){
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword());
Subject subject= SecurityUtils.getSubject();
try{
subject.login(token);
}catch (Exception e){
return e.getMessage() + "登陆失败";;
}
}
@GetMapping(value = "/getRoles")
public String getRoles(@RequestHeader("token")String token){
Long userid = JWT.unsign(token,Long.class);
UserEntity user = userService.queryById(userid);
UsernamePasswordToken upt = new UsernamePasswordToken(user.getName(),user.getPassword());
Subject subject= SecurityUtils.getSubject();
try{
subject.login(upt);
}catch (Exception e){
return e.getMessage();
}
List<String> roles = userService.queryUserRoles(userid);
return roles.toString();
}
参考博文:Shiro原理简析