前言

本篇文章将教大家在 shiro + springBoot 的基础上整合 JJWT (JSON Web Token)

JWT

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。

我们利用一定的编码生成 Token,并在 Token 中加入一些非敏感信息,将其传递。

一个完整的 Token :
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

在本项目中,我们规定每次请求时,需要在请求头中带上 token ,通过 token 检验权限,如没有,则说明当前为游客状态(或者是登陆 login 接口等)

maven

下面是本次整合使用到的工具包

     <!--JjWT--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- shiro --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><!--整合mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency>

JWTUtil

我们自定义一个jwtutil工具类实现token生成和解析

public class JWTUtil {// 过期时间5分钟private static final long EXPIRE_TIME = 5*60*1000;//私钥private static final String SECRETKEY = "imcode";/*** token解析* @param token token* @return io.jsonwebtoken.Claims*/public static Claims verify(String token){Claims claims;try {claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(SECRETKEY)).parseClaimsJws(token).getBody();}catch (ExpiredJwtException e){throw new AuthenticationException("token 过期"+e.getMessage());}catch (UnsupportedJwtException e){throw new AuthenticationException("token 无效"+e.getMessage());}catch (MalformedJwtException e){throw new AuthenticationException("token 格式错误"+e.getMessage());}catch (SignatureException e){throw new AuthenticationException("token 签名无效"+e.getMessage());}catch (IllegalArgumentException e){throw new AuthenticationException("token 参数异常"+e.getMessage());}catch (Exception e){throw new AuthenticationException("token 令牌错误"+e.getMessage());}return claims;}/*** 生成签名,5min后过期* @param username 用户名* @return 加密的token*/public static String sign(String username) {JwtBuilder jwt = Jwts.builder();//设置token唯一标识jwt.setId(UUID.randomUUID().toString());//设置主题jwt.setSubject(username);//设置签发者jwt.setIssuer(SECRETKEY);//设置签发日期jwt.setIssuedAt(new Date());//设置过期时间jwt.setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME));//私钥byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(SECRETKEY);//签名jwt.signWith(SignatureAlgorithm.HS256,secretKeyBytes);return jwt.compact();}
}

JWTToken

自定义一个token类替代shiro本身的userPasswordToken

public class JWTToken implements AuthenticationToken {private String token;public JWTToken(String token){this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

JWTFilter

然后自定义一个过滤器将拦截到的请求用我们自己的逻辑处理

@Slf4j
public class JWTFilter extends AccessControlFilter {// 登录标识private static String LOGIN_SIGN = "Authorization";/*** 是否允许通过,因为是无状态所以默认不通过,去自动登陆,返回false,调用onAccessDenied方法* @param request* @param response* @param mappedValue* @return boolean*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {return false;}@Overrideprotected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {//使用token登陆try {HttpServletRequest req = (HttpServletRequest) servletRequest;String authorization = req.getHeader(LOGIN_SIGN);JWTToken token = new JWTToken(authorization);// 提交给realm进行登入,如果错误他会抛出异常并被捕获getSubject(servletRequest, servletResponse).login(token);}catch (AuthenticationException e){response401(e.getMessage(), servletResponse);return false;}// 如果没有抛出异常则代表登入成功,返回truereturn true;}/*** 对跨域提供支持*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(HttpStatus.OK.value());return false;}return super.preHandle(request, response);}/*** 将非法请求跳转到 /401*/private void response401(String erroInfo, ServletResponse resp) {try {HttpServletResponse response = (HttpServletResponse) resp;response.setContentType("json/html;charset=utf8");response.setCharacterEncoding("utf8");PrintWriter pw = response.getWriter();Msg msg = new Msg();msg.setStatus("401");msg.setMessage(erroInfo);String result = JSON.toJSONString(msg);pw.write(result);} catch (IOException e) {log.error(e.getMessage());}}
}

MyRealm

自定义一个realm处理自己的登陆认证以及授权逻辑

@Service
public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;@Autowiredprivate ResourceService resourceService;//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();Claims claims = (Claims) principalCollection.getPrimaryPrincipal();String username = claims.getSubject();if ("admin".equals(username)) {Set<String> permissions = resourceService.list().stream().map(ResourceEntity::getPermission).collect(Collectors.toSet());info.addStringPermissions(permissions);info.addRole("admin");return info;}//2.查询数据库用户QueryWrapper<UserEntity> wrapper = new QueryWrapper<>();wrapper.eq("username", username);UserEntity user = userService.getOne(wrapper);String userId = user.getUserId();Set<String> permissions = userService.getPermissions(userId);info.addStringPermissions(permissions);return info;}//登陆认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {//1.获取用户输入的账户信息String token = (String) authenticationToken.getPrincipal();Claims claims = JWTUtil.verify(token);return new SimpleAuthenticationInfo(claims, token, getName());}/*** 找它的原因是这个方法返回true*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JWTToken;}
}

ShiroConfig

配置shiro内容,因为使用jwt方式登陆是无状态的,所以配置中禁用了session会话

@Configuration
public class ShiroConfig {//1.创建ShiroFilterFactory@Bean("shiroFilter")public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();// 添加自己的过滤器并且取名为jwtMap<String, Filter> filterMap = new HashMap<>();filterMap.put("jwt", new JWTFilter());factoryBean.setFilters(filterMap);factoryBean.setSecurityManager(securityManager);factoryBean.setUnauthorizedUrl("/user/unauthorized");/** 自定义url规则* http://shiro.apache.org/web.html#urls-*/Map<String, String> filterRuleMap = new LinkedHashMap<>();filterRuleMap.put("/user/login","anon");filterRuleMap.put("/user/unauthorized", "anon");filterRuleMap.put("/user/logout", "anon");// 所有请求通过我们自己的JWT FilterfilterRuleMap.put("/**", "jwt");// 访问401和404页面不通过我们的FilterfactoryBean.setFilterChainDefinitionMap(filterRuleMap);return factoryBean;}@Beanpublic CredentialMatcher credentialMatcher() {//自定义的密码比较器return new CredentialMatcher();}@Bean("securityManager")public DefaultWebSecurityManager getManager(MyRealm realm) {DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
//        realm.setCredentialsMatcher(credentialMatcher());// 使用自己的realmmanager.setRealm(realm);/** 关闭shiro自带的session,详情见文档* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29*/DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);manager.setSubjectDAO(subjectDAO);return manager;}/*** 下面的代码是添加注解支持*/@Bean@DependsOn("lifecycleBeanPostProcessor")public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();// 强制使用cglib,防止重复代理和可能引起代理出错的问题// https://zhuanlan.zhihu.com/p/29161098defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return defaultAdvisorAutoProxyCreator;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}
}

MD5加密

到此shiro已经整合完成,再整合一下md5密码加密功能

public class MD5Util {/*** 密码md5加密* @param password 原始密码* @param salt 盐* @return java.lang.String*/public static String createPasswd(String password,String salt){return new Md5Hash(password,salt,2).toString();}
}

controller添加用户和登陆接口

@RestController
@RequestMapping(value = "/user")
@Api(tags = {"系统用户管理"},value = "系统用户管理",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE
)
public class UserController {@Autowiredprivate UserService userService;@ApiOperation(value = "用户登陆")@PostMapping("/login")public Msg userLogin(@NotNull @RequestBody UserLoginDTO userLoginDTO) {QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username",userLoginDTO.getUsername());queryWrapper.eq("deleted",0);UserEntity user = userService.getOne(queryWrapper);if (null == user) throw new UnknownAccountException("用户名和密码错误");if (user.getStatus() == 1) throw new UnknownAccountException("用户被禁用");if (user.getStatus() == 2) throw new UnknownAccountException("用户被锁定");//将传入密码加密与数据库密码对比String password = MD5Util.createPasswd(userLoginDTO.getPassword(),user.getSalt());if (!password.equals(user.getPassword())) {throw new UnknownAccountException("用户名和密码错误");}//签发token返回String token = JWTUtil.sign(userLoginDTO.getUsername());Map<String, String> resultMap = new HashMap<>();resultMap.put("Authorization", token);return Msg.success(resultMap);}@Transactional@RequiresRoles(value = "admin")@PostMapping("/add")@ApiOperation(value = "添加用户")public Msg addUser(@RequestBody UserAddDTO userAddDTO){//使用uuid随机生成6位的盐存入数据库String uuId = UUID.randomUUID().toString();String salt = uuId.substring(uuId.length()-6);//加密密码String password = MD5Util.createPasswd("2wsx@WSX",salt);userAddDTO.setSalt(salt);userAddDTO.setPassword(password);String result = userService.addUser(userAddDTO);return Msg.success(result);}//权限校验@RequiresPermissions(logical = Logical.AND, value = {"userlist:view", "userlist:edit"})@GetMapping(value = "/list")public Msg<PageInfo<UserEntity>> getUserList(UserQueryDTO queryDTO) {PageInfo<UserEntity> pageInfo = userService.getUserList(queryDTO);return Msg.success(pageInfo);}@GetMapping("/unauthorized")public Msg unauthorized() {CodeMessage codeMessage = CodeMessage.UNAUTHORIZED;Msg msg = new Msg();msg.setStatus(codeMessage.getStatus());msg.setMessage(codeMessage.getMessage());return msg;}
}

以上所有使用到的userService方法自己实现自己的逻辑,至此整合shiro与jjwt完成,仅供参考。

spring boot整合shiro+jjwt相关推荐

  1. Spring Boot整合Shiro + Springboot +vue

    目录 02 Spring Boot整合Shiro p1.shiro概述 1 什么是Shiro 2 Shiro核心组件 p2.Shiro实现登录认证 AccountRealm.java QueryWra ...

  2. Spring Boot 整合 shiro 之盐值加密认证详解(六)

    Spring Boot 整合 shiro 之盐值加密认证详解 概述 不加盐认证 加入密码认证核心代码 修改 CustomRealm 新增获取密文的方法 修改 doGetAuthenticationIn ...

  3. Spring Boot 整合 Shiro(三)Kaptcha验证码 附源码

    前言 本文是根据上篇<Spring Boot 整合Shiro(二)加密登录与密码加盐处理>进行修改,如有不明白的转上篇文章了解. 1.导入依赖 <!-- https://mvnrep ...

  4. 六、Spring Boot整合Shiro

    六.Spring Boot整合Shiro 6.1.整合思路 6.2.创建spring boot项目 6.3.引入shiro依赖 6.4.配置shiro环境 创建配置类ShiroConfig 1.配置: ...

  5. Spring Boot整合Shiro + JSP教程(用户认证,权限管理,图片验证码)

    在此首先感谢**编程不良人**up主提供的视频教程 代码都是跟着up的视频敲的,遇到的一些问题也是通过CSDN博主提供的教程解决的,在此也感谢那些提供bug解决方案的前辈们~ 项目完整代码已经发布到g ...

  6. spring boot整合shiro继承redis_Springboot+Shiro+redis整合

    1.Shiro是Apache下的一个开源项目,我们称之为Apache Shiro.它是一个很易用与Java项目的的安全框架,提供了认证.授权.加密.会话管理,与spring Security 一样都是 ...

  7. spring boot整合Shiro实现单点登录

    默认情况下,Shiro已经为我们实现了和Cas的集成,我们加入集成的一些配置就ok了 1.加入shiro-cas包 <!-- shiro整合cas单点 --><dependency& ...

  8. Spring Boot 整合 Shiro

    虽然,直接用Spring Security和SpringBoot 进行"全家桶式"的合作是最好不过的,但现实总是欺负我们这些没办法决定架构类型的娃子. Apache Shiro 也 ...

  9. spring boot整合shiro继承redis_spring-boot-plus集成Shiro+JWT权限管理

    SpringBoot+Shiro+JWT权限管理 Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以 ...

最新文章

  1. 基于Golang的对象序列化的程序包开发——myJsonMarshal
  2. 入门单片机选择51还是stm32?入门单片机有哪些好的教学视频?
  3. 标记三维点_三维扫描仪对汽车钣金外形检测折弯件钣金件热成型加工件的应用...
  4. Android艺术——性能优化问题
  5. linux条件表达式例子,Linux的Iptables命令的基本知识(三)-常用匹配条件示例和执行动作...
  6. 艺术签名python_个性签名设计五十行Python轻松实现
  7. Mysql主从异常 表被回滚_oracle表回滚到一个指定时间的操作语句 oracle 误删除数据恢复...
  8. python的结构_Python结构的选择,python,之
  9. 职业高中计算机英语教案,职高英语shopping教学设计.doc
  10. 一款不错的开源 Laravel 后台面板/CMS系统 —— LaraAdmin
  11. Python poetry的使用
  12. TensorFlow by Google过拟合优化 Machine Learning Foundations: Ep #7 - Image augmentation and overfitting
  13. 深度置信网络(Deep belief network)matlab初解
  14. KT148A语音芯片ic的软件参考代码C语言,一线串口
  15. 微型计算机三部分基本组成,微型计算机的基本组成-电脑自学网
  16. archlinux 触摸板设置
  17. 公众号管理系统 v1.0.0
  18. linux中创建目录树,linux怎样创建目录树
  19. 基于动态时间规整(DTW)的孤立字语音识别
  20. csp-s模拟测试49(9.22)养花(分块/主席树)·折射(神仙DP)·画作

热门文章

  1. CCF推荐+SCI二区, IVC和EAAI期刊特刊征稿, 全部明年3月出录用结果!
  2. java键盘输入语句_java键盘输入语句怎么写?
  3. JQuery的submitHandler
  4. 给乌龟喂食卡通HTML5js特效
  5. 高速PCB 阻抗计算
  6. Fikkernbsp;你的网站加速利器
  7. python程序设置头像_Django+JS 实现点击头像即可更改头像的方法示例
  8. 我的大学(四)——反思与回顾
  9. android 盲人辅助,微光盲人无障碍生活辅助
  10. Nginx+DNS负载均衡