Shrio是一个轻量级的,基于AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。

JWT(JSON Web Token)是目前最流行的跨域身份验证解决方案,具有加密和自包含的特性。

1.maven配置

<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.</version>
</dependency> <!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.</version>
</dependency>

2.自定义Token Authentication

package com.bby.security;

import org.apache.shiro.authc.AuthenticationToken;

/**
* 自定义jwt类型的token
*
* @author: zhangyang
* @create: 2018/11/28 8:39
**/
public class MyJWTToken implements AuthenticationToken {
private String token; public MyJWTToken(String token) {
this.token = token;
} @Override
public Object getPrincipal() {
return token;
} @Override
public Object getCredentials() {
return token;
}
}

3.自定义Shiro过滤器

package com.bby.security;

import com.alibaba.fastjson.JSONObject;
import com.bby.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.util.AntPathMatcher;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter; /**
* jwt token filter
*
* @Author zhangyang
* @Date 下午 8:42 2018/11/27 0027
**/
@Slf4j
public class MyJWTFilter extends BasicHttpAuthenticationFilter { private String tokenHeader;
private String loginUri; public MyJWTFilter(String tokenHeader, String loginUri) {
this.tokenHeader = tokenHeader;
this.loginUri = loginUri;
} /**
* 如果是登录则直接放行;
* 如果带有 token,则对 token 进行检查
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest req = (HttpServletRequest) request;
AntPathMatcher matcher = new AntPathMatcher();
// 开放登录接口访问
if (matcher.match(loginUri, req.getRequestURI())) {
return true;
}
// 判断请求的请求头是否带上token
if (StringUtils.isBlank(req.getHeader(tokenHeader))) {
return false;
} // 如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
try {
executeLogin(request, response);
} catch (Exception e) {
log.error(e.getMessage());
try {
// globalExceptionHandler无法处理filter中的异常,这里手动处理
PrintWriter out = response.getWriter();
out.print(JSONObject.toJSON(Result.failureWithCode(Result.UNAUTHORIZED, e.getMessage())));
out.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
return false;
}
return true;
} /**
* 执行登陆操作
*
* @param request
* @param response
* @return
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader(tokenHeader);
MyJWTToken jwtToken = new MyJWTToken(token);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(jwtToken);
// 如果没有抛出异常则代表登入成功,返回true
return true;
}
}

4.自定义Shiro Realm

package com.bby.security;

import com.bby.common.util.RedisRepository;
import com.bby.mapper.system.SysUserMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import java.util.List; /**
* 实现AuthorizingRealm接口用户用户认证
*
* @author: zhangyang
* @create: 2018/11/24 21:25
**/
@Component
public class MyRealm extends AuthorizingRealm { @Autowired
private RedisRepository redisRepository; @Autowired
private SysUserMapper sysUserMapper; /**
* 获取用户权限信息
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取token
String token = (String) principalCollection.getPrimaryPrincipal();
// 查询用户权限信息
List<String> permissions = sysUserMapper.getPermissionByUserId(JWTUtil.getUserId(token)); // 只添加权限(角色方式不灵活)
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
if (CollectionUtils.isNotEmpty(permissions)) {
simpleAuthorizationInfo.addStringPermissions(permissions);
}
return simpleAuthorizationInfo;
} /**
* 获取用户认证信息
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 加这一步的目的是在Post请求的时候会先进认证,然后在到请求
Object principal = authenticationToken.getPrincipal();
if (principal == null) {
return null;
} String token = (String) principal;
// 解密获得username,用于和数据库进行对比
String claim = JWTUtil.getUserId(token);
// 验证缓存中的登录状态
if (claim == null
|| !JWTUtil.verify(token, claim)
|| !redisRepository.exists(token)) {
throw new AuthenticationException("token validation failed");
} // 获取用户信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token, token, getName());
return simpleAuthenticationInfo;
} /**
* 设置支持的token类型为自定义jwtToken
*
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof MyJWTToken;
} /**
* 覆盖验证密码是否匹配的方法,因为在自定义的login方法中已经实现了
*
* @param token
* @param info
* @throws AuthenticationException
*/
@Override
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
}
}

5.登录代码

package com.bby.controller;

import com.bby.common.vo.Result;
import com.bby.security.JWTUtil;
import com.bby.service.system.ISysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest;
import java.util.Map; /**
* 登录/鉴权controller
*
* @author: zhangyang
* @create: 2018/11/25 20:26
**/
@Api(tags = "认证授权")
@RestController
@RequestMapping("auth")
public class AuthController { @Autowired
private ISysUserService sysUserService; /**
* 登录
*
* @param authInfo
* @return
*/
@ApiOperation("登录")
@PostMapping("login")
public Result login(@ApiParam("用户名") @RequestBody Map<String, String> authInfo) {
return sysUserService.validate(authInfo.get("username"), authInfo.get("password"));
} /**
* 登出
*
* @return
*/
@ApiOperation("登出")
@PostMapping("logout")
public Result logout(HttpServletRequest request) {
// 因为token的方式是无状态的,要实现登出,则需要使用缓存来保存状态
return sysUserService.logout(JWTUtil.getToken(request));
} /**
* 用户信息
* 用户名:
* @return
*/
@ApiOperation("获取用户信息")
@GetMapping("info")
public Result info(HttpServletRequest request) {
return sysUserService.getAuthInfo(JWTUtil.getUserId(JWTUtil.getToken(request)));
}
}

6.shiro配置

package com.bby.security;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn; import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map; /**
* shiro 配置
*
* @author: zhangyang
* @create: 2018/11/24 21:34
**/
@Configuration
public class ShiroConfiguration {
@Value("${jwt.token-header}")
private String tokenHeader; @Value("${jwt.filter-name}")
private String jwtFilterName; @Value("${login.uri}")
private String loginUri; /**
* 将自己的验证方式加入容器
*
* @return
*/
@Autowired
private MyRealm myRealm; /**
* 权限管理,配置主要是Realm的管理认证
*
* @return
*/
@Bean
@DependsOn("myRealm")
public SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 使用自己的realm
System.out.println(myRealm);
manager.setRealm(myRealm); // 关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
} /**
* Filter工厂,设置对应的过滤条件和跳转条件
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new HashMap<>();
System.out.println(tokenHeader);
filterMap.put("jwt", new MyJWTFilter(tokenHeader, loginUri));
factoryBean.setFilters(filterMap); factoryBean.setSecurityManager(securityManager);
factoryBean.setUnauthorizedUrl("/401"); Map<String, String> filterRuleMap = new HashMap<>();
// 所有请求通过我们自己的JWT Filter
filterRuleMap.put("/**", "jwt");
// 访问401和404页面不通过我们的Filter
filterRuleMap.put("/401", "anon");
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
} /**
* 下面的代码是添加注解支持
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
// https://zhuanlan.zhihu.com/p/29161098
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
} @Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}

spring-boot集成8:集成shiro,jwt的更多相关文章

  1. Spring Boot 最简单整合Shiro+JWT方式

    简介 目前RESTful大多都采用JWT来做授权校验,在Spring Boot 中可以采用Shiro和JWT来做简单的权限以及认证验证,在和Spring Boot集成的过程中碰到了不少坑.便结合自身以 ...

  2. spring boot rest 接口集成 spring security(2) - JWT配置

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  3. Spring Boot HikariCP 一 ——集成多数据源

    其实这里介绍的东西主要是参考的另外一篇文章,数据库读写分离的. 参考文章就把链接贴出来,里面有那位的代码,简单明了https://gitee.com/comven/dynamic-datasource ...

  4. spring boot rest 接口集成 spring security(1) - 最简配置

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  5. spring boot / cloud (三) 集成springfox-swagger2构建在线API文档

    spring boot / cloud (三) 集成springfox-swagger2构建在线API文档 前言 不能同步更新API文档会有什么问题? 理想情况下,为所开发的服务编写接口文档,能提高与 ...

  6. Spring Boot系列——如何集成Log4j2

    上篇<Spring Boot系列--日志配置>介绍了Spring Boot如何进行日志配置,日志系统用的是Spring Boot默认的LogBack. 事实上,除了使用默认的LogBack ...

  7. 【ELK】4.spring boot 2.X集成ES spring-data-ES 进行CRUD操作 完整版+kibana管理ES的index操作

    spring boot 2.X集成ES 进行CRUD操作  完整版 内容包括: ============================================================ ...

  8. 15、Spring Boot 2.x 集成 Swagger UI

    1.15.Spring Boot 2.x 集成 Swagger UI 完整源码: Spring-Boot-Demos 1.15.1 pom文件添加swagger包 <swagger2.versi ...

  9. 14、Spring Boot 2.x 集成 Druid 数据源

    14.Spring Boot 2.x 集成 Druid 数据源 完整源码: Spring-Boot-Demos

  10. 12、Spring Boot 2.x 集成 MongoDB

    1.12 Spring Boot 2.x 集成 MongoDB 完整源码: Spring-Boot-Demos

随机推荐

  1. body element height id small, but the backgroud color is full screen

    http://www.cnblogs.com/xiaoyuersdch/p/9156240.html ------------------------------------------------- ...

  2. ECMAScript 6 入门——ES6 声明变量的六种方法

    ES6 声明变量的六种方法 ES5 只有两种声明变量的方法:var命令和function命令.ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和cla ...

  3. 微信小程序 base64ToArrayBuffer

    base64ToArrayBuffer 将 base64 的字符串转化为 ArrayBuffer 对象 示例代码: 使用位置:在 JS文件的任意函数中使用 const base64 = 'CxYh'; ...

  4. 【经典dp 技巧】8.13序列

    经典的拆绝对值 题目大意 给定$n$个具有顺序的序列,允许对每个序列循环移动.记第$i$个序列尾元素为$x$,$i+1$个序列首元素为$y$,定义其连接收益为$|x-y|*i$,求$n$个序列连接最大 ...

  5. UVa1078 Steam Roller——拆点+最短路

    题目链接 思路 把每个点拆成\(5\)个点\(id(x,y),id(x,y)+n,id(x,y)+2*n,id(x,y)+3*n,id(x,y)+4*n\),分别表示到这个点时的方向为上,右,下,左和 ...

  6. Luogu P2824 [HEOI2016/TJOI2016]排序 线段树+脑子

    只会两个$log$的$qwq$ 我们二分答案:设答案为$ans$,则我们把$a[i]<=ans$全部设成$0$,把$a[i]>ans$全部设成$1$,扔到线段树里,这样区间排序(升序)就是 ...

  7. msyql round函数隐藏问题

    1.背景 在用mysql round进行四舍五入计算的时候如果参与计算的字段为float,则最终计算出的四舍五入效果会有很大出入.例子我就不列举了 2.原因 mysql官方文档中关于ROUND函数的部 ...

  8. 多线程中volatile关键字的作用

    原文链接:https://blog.csdn.net/xuwentao37x/article/details/27804169 多线程的程序是出了名的难编写.难验证.难调试.难维护,这通常是件苦差事. ...

  9. Hdu 4333 Revolving Digits(Exkmp)

    Revolving Digits Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) To ...

  10. AGC032F One Third

    很奇怪的一个题.看见了无从下手.概率期望好题. 给一个面积为 \(1\) 的圆,经过圆心随机幅角切直径 \(n\) 次,定义 \(f(x) = \min |S - \frac{1}{3}|\),其中 ...