本文源码:github
上一篇Shiro的demo:SpringMVC框架下使用shiro权限管理
最近一直在研究Shiro,因为项目里面要使用Shiro进行权限控制。由于项目不只是在Web端运行,还会在移动端App使用,想要使用JWT方式进行无状态的RESTful API交互,也就是登录后生成token并返回给前端,前端每次请求时都在请求头里面添加token,后端验证有效性。所以Shiro自带的Session要禁用掉,同时要重新写JWT的过滤器。
至于shiro的介绍,搬来官网介绍:https://shiro.apache.org/introduction.html
如上介绍,由于使用JWT需要禁用session,所以该demo只是使用了认证Authentication和授权Authorization两大块。
一开始直接照着网上的例子做demo,一直出错。所以放弃shiro写了个拦截器用来拦截,只是没法细粒度的控制权限,如下:
package filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import util.JwtUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ControllerInterception extends HandlerInterceptorAdapter {
private Logger logger = LoggerFactory.getLogger(ControllerInterception.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getServletPath();
logger.info("url"+url);
String token = request.getHeader("token");
logger.info("开始验证token");
if (JwtUtil.verifyToken(token)) {
logger.info("验证token通过");
String role = (String) JwtUtil.parseToken(token).get("role");
logger.info("role"+role);
if (url.indexOf(role) == 1){
logger.info("匹配url通过");
return true;
}
}
logger.info("验证token失败");
response.sendRedirect(request.getContextPath()+"/login");
return false;
}
}
<!--登录拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截器拦截的类型-->
<mvc:mapping path="/**"/>
<!--不拦截以下请求-->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/index"/>
<bean id="controllerInterception" class="filter.ControllerInterception"/>
</mvc:interceptor>
</mvc:interceptors>
但是不想放弃使用shiro,所以自己一直在研究到底禁用session后shiro如何将JWT传递并在哪里校验。最终整理出来后才发现是如此的简单。说一下,使用shiro+ssm+jwt的一些前期准备:
一、首先,禁用shiro的session,先添加shiro的依赖到pom
<!--Shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
按照网上的说法要写一个类禁用(http://jinnianshilongnian.iteye.com/blog/2041909)
一开始就是按照这种写法,但是会报错,所以上面的类就省略了,但是不影响使用JWT。我是按下面的实现直接写在spring的xml文件中的,并没有实现上面的类:
<!--Shiro-->
<!-- Realm实现 -->
<bean id="statelessRealm" class="shiro.MyStatelessRealm">
<property name="cachingEnabled" value="false"/>
</bean>
<!-- 禁用掉会话调度器 -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
<property name="sessionValidationSchedulerEnabled" value="false"/>
</bean>
<!--解决报错,组装默认的subjectDAO-->
<bean id="subjectDAO" class="org.apache.shiro.mgt.DefaultSubjectDAO">
<property name="sessionStorageEvaluator" ref="sessionStorageEvaluator"/>
</bean>
<bean id="sessionStorageEvaluator" class="org.apache.shiro.mgt.DefaultSessionStorageEvaluator">
<property name="sessionStorageEnabled" value="false"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="statelessRealm"/>
<property name="subjectDAO" ref="subjectDAO"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- 相当于调用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>
二、其次:重写filter,看里面的注释,应该可以看懂:
package shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.JwtUtil;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class MyStatelessShiroFilter extends AccessControlFilter {
private Logger logger = LoggerFactory.getLogger(MyStatelessShiroFilter.class);
/**
*返回false
* @param servletRequest
* @param servletResponse
* @param o
* @return 返回结果是false的时候才会执行下面的onAccessDenied方法
* @throws Exception
*/
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
logger.info("is access allowed");
return false;
}
/**
* 从请求头获取token并验证,验证通过后交给realm进行登录
* @param servletRequest
* @param servletResponse
* @return 返回结果为true表明登录通过
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
logger.info("on access denied");
HttpServletRequest request = (HttpServletRequest) servletRequest;
String jwt = request.getHeader("Authorization");
if (JwtUtil.verifyToken(jwt)) {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(jwt, jwt);
try {
//委托realm进行登录认证
getSubject(servletRequest, servletResponse).login(usernamePasswordToken);
return true;
}catch (Exception e) {
return false;
}
}
redirectToLogin(servletRequest,servletResponse);
return false;
}
/**
* 重定向到登录页
* @param request
* @param response
* @throws IOException
*/
@Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
logger.info("redirectToLogin");
WebUtils.issueRedirect(request, response, "/login");
}
}
这里面使用依然是shiro自带的token实现UsernamePasswordToken,自定义的token用来用去都用糊涂了,还是自带的好用一些。
这个时候就加入配置文件即可,statelessAuth就是自己的filter:
<bean id="myStatelessFilter" class="shiro.MyStatelessAuthcFilter"/>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="filters">
<util:map>
<entry key="statelessAuth" value-ref="myStatelessFilter"/>
</util:map>
</property>
<property name="loginUrl" value="/shirologin"/>
<property name="unauthorizedUrl" value="/unauthorized"/>
<property name="filterChainDefinitions">
<value>
/=anon
/shirologin=anon
/unauthorized=anon
/user/**=statelessAuth,roles[user]
/admin/**=statelessAuth,roles[admin]
/**=statelessAuth
</value>
</property>
</bean>
web.xml中也要配置filter,要和这里面的filter命名要一致:
<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.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>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
三、再次,就是自定义realm,实现认证和授权,这里的role我是写死的,省的查验数据库:
package shiro;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyStatelessRealm extends AuthorizingRealm {
Logger logger = LoggerFactory.getLogger(MyStatelessRealm.class);
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("Realm处理登录");
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String token = usernamePasswordToken.getUsername();
return new SimpleAuthenticationInfo(token, token, getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("Realm处理授权");
String token = (String) principalCollection.getPrimaryPrincipal();
logger.info("realm授权获取token:"+token);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRole("user");
return authorizationInfo;
}
}
四、这时候就要写一些controller进行测试了,使用Json格式测试的:
package controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import util.JwtUtil;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ShiroController {
Logger logger = LoggerFactory.getLogger(ShiroController.class);
@RequestMapping("shirologin")
public String toLogin() {
return "you must login firstly";
}
@RequestMapping(value = "shirologin",method = RequestMethod.POST)
public String login(String username, String password) {
logger.info("shiro登录");
Map<String, Object> map = new HashMap<>();
map.put("username",username);
String token = new JwtUtil().createJwt(map);
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(token, token);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(usernamePasswordToken);
} catch (Exception e) {
return "login fail ex:"+e.getMessage();
}
return "login success ! token is :"+token;
}
@RequestMapping("user")
public String toUser(){
logger.info("进入 user");
return "welcome to user";
}
@RequestMapping("admin")
public String toAdmin(){
return "welcome to admin";
}
@RequestMapping("unauthorized")
public String unAuth(){
return "unauthorized";
}
}
五、使用postman进行接口测试:
手机扫一扫
移动阅读更方便
你可能感兴趣的文章