本文源码: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;
}
}
但是不想放弃使用shiro,所以自己一直在研究到底禁用session后shiro如何将JWT传递并在哪里校验。最终整理出来后才发现是如此的简单。说一下,使用shiro+ssm+jwt的一些前期准备:
一、首先,禁用shiro的session,先添加shiro的依赖到pom
org.apache.shiro
shiro-core
1.4.0
org.apache.shiro
shiro-web
1.4.0
org.apache.shiro
shiro-spring
1.4.0
org.apache.shiro
shiro-ehcache
1.4.0
按照网上的说法要写一个类禁用(http://jinnianshilongnian.iteye.com/blog/2041909)
一开始就是按照这种写法,但是会报错,所以上面的类就省略了,但是不影响使用JWT。我是按下面的实现直接写在spring的xml文件中的,并没有实现上面的类:
二、其次:重写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:
/=anon
/shirologin=anon
/unauthorized=anon
/user/**=statelessAuth,roles[user]
/admin/**=statelessAuth,roles[admin]
/**=statelessAuth
web.xml中也要配置filter,要和这里面的filter命名要一致:
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
三、再次,就是自定义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 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进行接口测试: