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
-
添加依赖
org.apache.shiro shiro-all 1.3.2 net.sf.ehcache ehcache 2.10.6 -
shiroFilter org.springframework.web.filter.DelegatingFilterProxy shiroFilter /*web.xml
中添加过滤器(Filter
) -
配置
<?xml version="1.0" encoding="UTF-8"?>spring-shiro.xml
<!-- 定义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>
-
ApplicationContext.xml
中加入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 roles = userService.queryUserRoles(userid);
return roles.toString();
}
参考博文:Shiro原理简析