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. switch结构

    switch结构介绍 switch也属于条件判断的语句 支持多种写法,和if .. else if ...else 结构的功能类似,但是里面的细节需要注意的地方更多 switch基本语法 switch ...

  2. python+Appium自动化:H5元素定位

    问题思考 在混合开发的App中,经常会有内嵌的H5页面.那么这些H5页面元素该如何进行定位操作呢? 解决思路 针对这种场景直接使用前面所讲的方法来进行定位是行不通的,因为前面的都是基于Andriod原 ...

  3. HTML5——3 HTML5拖放

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. C# 内存管理和指针 (13)

    本章要点 运行库在栈和堆上分配空间 垃圾回收 使用析构函数 和 SYstem.IDisposable 接口来释放非托管的资源 C#中使用指针的语法 使用指针实现基于栈的高性能数组 值类型数据 程序第一 ...

  5. webstorm 2016.3 注册方法

    用license server 的方式吧,activation code 的方式没有找到方法. license server 里写http://idea.iteblog.com/key.php 用li ...

  6. Java学习 1.5——静态Static的作用与用法

    在网上查阅资料,静态大约分为这几部分:修饰成员变量:修饰成员方法:静态代码块:静态导包,下面会用代码一一展示: 修饰成员变量: 一个类中,使用static修饰成员变量后,该变量变为全局变量,当再次ne ...

  7. off(events,[selector],[fn]) 在选择元素上移除一个或多个事件的事件处理函数。

    off(events,[selector],[fn]) 概述 在选择元素上移除一个或多个事件的事件处理函数. off() 方法移除用.on()绑定的事件处理程序.有关详细信息,请参阅该网页上deleg ...

  8. 分布式hadoop 架构图

    zk:zookeeper NN:namenode DN:datanode ZF Failover Controller:简称zkfc ,是zk的客户端,只运行在主备的namenode上,主要作用是判断 ...

  9. MySQL初识数据库

    为什要用数据库 第一,将文件和程序存在一台机器上是很不合理的. 第二,操作文件是一件很麻烦的事 你可以理解为 数据库 是一个可以在一台机器上独立工作的,并且可以给我们提供高效.便捷的方式对数据进行增删 ...

  10. [CSP-S2019]:赛后总结

    笔者有幸参加了$CSP-S\ 2019$,$AFO$之前,写下自己最后一篇赛后总结. $Day\ 0$ 早上起来把自己调了一晚上被卡空间的题卡过了,很开心(内存限制$256MB$,然而我的内存申请是$ ...