使用springboot整合shiro和token实现用户的身份验证和权限控制
使用shiro请看:https://blog.csdn.net/sqlgao22/article/details/98506479
使用token请看:https://blog.csdn.net/sqlgao22/article/details/98532943
由于已经分别学习了shiro和token想到整合在一起使用会很好.
文章参考了:https://www.jianshu.com/p/0b1131be7ace,
这位老哥写的很详细,有点深奥,我就简单的做了,没有做太深,需要深入研究的可以看看老哥写的.
其实使用shiro的过滤器来判断token是否存在以及正确最重要的就是自定义shiro的过滤器,并且加入过滤器链.
用户的登录不需要使用到token,所有在用户身份验证(登录)环节不用使用到token,但是要额外的为已经登录的用户返回一个token.
在用户访问阶段需要做的就是,使用shiro的过滤器验证token的真实性,已经用户的权限.
项目依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--jjwt使用-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
自定义的realm
用于权限和身份的信息的提供.
如何不是很懂,为何自定义realm去看看前面的文章.
public class MyRealm extends AuthorizingRealm {
@Resource
private RolesMapper rolesMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("============用户授权==============");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
/*获取当前的用户,已经登录后可以使用在任意的地方获取用户的信息*/
String username = (String) SecurityUtils.getSubject().getPrincipal();
/*查询用户的权限*/
Roles role = rolesMapper.getRole(username);
/*将role放在一个集合中,多个权限使用集合*/
info.addRole(role.getRole());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("============用户验证==============");
//从token中获取信息,此token只是shiro用于身份验证的,并非前端传过来的token.
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
String password = rolesMapper.getPassword(username);
if (null == password) {
throw new AuthenticationException("doGetAuthenticationInfo中的用户名不对");
} else if (!password.equals(new String(token.getPassword()))){
throw new AuthenticationException("doGetAuthenticationInfo中的密码不对");
}
//组合一个验证信息
System.out.println("token.getPrincipal()默认返回的username======"+token.getPrincipal());
System.out.println("getName()"+getName());
SimpleAuthenticationInfo info =
new SimpleAuthenticationInfo(token.getPrincipal(),password,getName());
return info;
}
}
自定义filter
这个重点在于你要继承哪个shiroFilter,主要还是根据需求进行不同的继承,如果自定义的内容很多还是继承较高的父级,如果功能需求不多,尽量还是使用shiro过滤器的一些原有的功能,我自己尝试了几个,最后继承了最终的实现类AuthenticationFilter;这过滤器就是我们过滤器链上key为的authc的过滤器.同时也对ajax做了区分.不能给ajax返回一个页面吧.
说明一下:这两个重写的方法.filter是否允许是由这两个方法决定的.
底层代码是:
return isAccessAllowed() || onAccessDenied()
isAccessAllowed:是否允许访问,当为true时就会允许,不需要执行onAccessDenied了.
onAccessDenied:只有当isAccessAllowed为false为false时才会执行,所以可以理解为,不允许访问后要怎么告诉用户的方法.或是一些其他的操作.
public class ClientShiroThree extends AuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse response1) throws Exception {
HttpServletResponse response = (HttpServletResponse) response1;
HttpServletRequest request = (HttpServletRequest) servletRequest;
String ajax = request.getHeader("x-requested-with");
if (null==ajax) {
System.out.println("=====不是ajax");
response.sendRedirect("/login");
}else {
System.out.println("=====是ajax"+ajax);
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("访问有问题");
}
return false;
}
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse response, Object mappedValue) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader(TokenUtil.tokenHeard);
System.out.println("================"+token);
if (null == token||"".equals(token)) {
System.out.println("-------------------token为空");
return false;
}
//验证token的真实性
try {
TokenUtil.getTokenBody(token);
} catch (Exception e) {
e.printStackTrace();
System.out.println("----------------token有问题");
return false;
}
return true;
}
}
然后进行shiro配置
这个重点就是如何将自定义的filter加入过滤链,
其实就是先取出来链,加一个,在放进去!!!
@Configuration
public class ShiroConfig {
@Bean
public Realm myRealm() {
return new MyRealm();
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm());
return manager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
filter.setSecurityManager(securityManager());
//加入自定义的filter
Map<String, Filter> filterMap = filter.getFilters();
filterMap.put("client", new ClientShiroThree());
filter.setFilters(filterMap);
//定义相关路径
filter.setLoginUrl("/login");
filter.setUnauthorizedUrl("/noAuthorize");
//定义拦截路径,记得将静态资源也排除过滤
/*进行权限的控制,必须使用LinkHashMap,shrio要按照顺序进行设置*/
Map<String, String> authMap = new LinkedHashMap<>();
authMap.put("/guest/**", "anon");
authMap.put("/static/**", "anon");
authMap.put("/user/**", "client,roles[user]");
authMap.put("/admin/**", "client,roles[admin]");
authMap.put("/login", "anon");
authMap.put("/**", "client");
filter.setFilterChainDefinitionMap(authMap);
//配置完成
System.out.println("---------------shirofactory创建成功");
return filter;
}
}
最后别忘了token的解析和生成
所有的信息都在token中储存,shiro只是负责验证.
public class TokenUtil {
private static final String secret = "secret";
public static final String tokenHeard = "tokenHead";
private static final Long expTime = 60 * 5 * 1000L;
public static String getToken(String name,String id,String ip) {
JwtBuilder builder = Jwts.builder();
builder.signWith(SignatureAlgorithm.HS256,secret);
builder.setId(id).setSubject(name).setAudience(ip);
builder.setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expTime));
String token = builder.compact();
return token;
}
public static Claims getTokenBody(String token) {
JwtParser parser = Jwts.parser();
Claims body = parser.setSigningKey(secret).parseClaimsJws(token).getBody();
return body;
}
public static String getName(String token) {
Claims body = getTokenBody(token);
String id = body.getId();
return id;
}
}
验证准备
package com.jd.controller;
import com.jd.dao.RolesMapper;
import com.jd.domain.Roles;
import com.jd.token.TokenUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
public class LoginController {
@Resource
private RolesMapper rolesMapper;
@RequestMapping("/get")
@ResponseBody
public Object get() {
List<Roles> roles = rolesMapper.getAllRoles();
return roles;
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String notLogin() {
return "login";
}
@RequestMapping("/main")
@ResponseBody
public String test() {
return "main";
}
@RequestMapping(value = "/noAuthorize")
@ResponseBody
public String notRole() {
return "您没有权限!";
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
@ResponseBody
public String logout() {
Subject subject = SecurityUtils.getSubject();
//注销
subject.logout();
return "成功注销!";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public String login(String name, String password, HttpServletRequest request, HttpServletResponse response) {
try {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(name, password);
subject.login(usernamePasswordToken);
Roles role = rolesMapper.getRole(name);
String token = TokenUtil.getToken(role.getName(), role.getId(), request.getRemoteAddr());
return token;
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
}
前端没有变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<script src="/static/jquery-3.4.1.min.js"></script>
<script>
/*设置token为全局变量*/
var token = ''
function fun02() {
var name = $("#name").val()
var password = $("#password").val()
$.ajax({
url: "/login",
type: "post",
data: {
name: name,
password: password
},
success: function (data) {
token = data
alert(token)
}
})
}
function fun01() {
$.ajax({
url: "/admin/msg",
type: "get",
data: {},
headers: {'tokenHead': token},
success: function (data) {
alert(data)
}
})
}
</script>
</head>
<body>
<form id="login">
姓名:<input type="text" name="username" id="name"><br>
密码:<input type="password" name="password" id="password"><br>
<input type="button" value="登录" onclick="fun02()">
</form>
<br>
<input type="button" value="ajax请求" onclick="fun01()">
</body>
</html>
验证
当访问login页面的时候正常,当没有登录时访问其他页面会报错,
当登录之后,就会返回token:
当再次访问的时候就会出现访问成功了.
当权限不够时,就会报错没有权限.