spring boot整合shiro+jjwt
前言
本篇文章将教大家在 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相关推荐
- Spring Boot整合Shiro + Springboot +vue
目录 02 Spring Boot整合Shiro p1.shiro概述 1 什么是Shiro 2 Shiro核心组件 p2.Shiro实现登录认证 AccountRealm.java QueryWra ...
- Spring Boot 整合 shiro 之盐值加密认证详解(六)
Spring Boot 整合 shiro 之盐值加密认证详解 概述 不加盐认证 加入密码认证核心代码 修改 CustomRealm 新增获取密文的方法 修改 doGetAuthenticationIn ...
- Spring Boot 整合 Shiro(三)Kaptcha验证码 附源码
前言 本文是根据上篇<Spring Boot 整合Shiro(二)加密登录与密码加盐处理>进行修改,如有不明白的转上篇文章了解. 1.导入依赖 <!-- https://mvnrep ...
- 六、Spring Boot整合Shiro
六.Spring Boot整合Shiro 6.1.整合思路 6.2.创建spring boot项目 6.3.引入shiro依赖 6.4.配置shiro环境 创建配置类ShiroConfig 1.配置: ...
- Spring Boot整合Shiro + JSP教程(用户认证,权限管理,图片验证码)
在此首先感谢**编程不良人**up主提供的视频教程 代码都是跟着up的视频敲的,遇到的一些问题也是通过CSDN博主提供的教程解决的,在此也感谢那些提供bug解决方案的前辈们~ 项目完整代码已经发布到g ...
- spring boot整合shiro继承redis_Springboot+Shiro+redis整合
1.Shiro是Apache下的一个开源项目,我们称之为Apache Shiro.它是一个很易用与Java项目的的安全框架,提供了认证.授权.加密.会话管理,与spring Security 一样都是 ...
- spring boot整合Shiro实现单点登录
默认情况下,Shiro已经为我们实现了和Cas的集成,我们加入集成的一些配置就ok了 1.加入shiro-cas包 <!-- shiro整合cas单点 --><dependency& ...
- Spring Boot 整合 Shiro
虽然,直接用Spring Security和SpringBoot 进行"全家桶式"的合作是最好不过的,但现实总是欺负我们这些没办法决定架构类型的娃子. Apache Shiro 也 ...
- spring boot整合shiro继承redis_spring-boot-plus集成Shiro+JWT权限管理
SpringBoot+Shiro+JWT权限管理 Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以 ...
最新文章
- 基于Golang的对象序列化的程序包开发——myJsonMarshal
- 入门单片机选择51还是stm32?入门单片机有哪些好的教学视频?
- 标记三维点_三维扫描仪对汽车钣金外形检测折弯件钣金件热成型加工件的应用...
- Android艺术——性能优化问题
- linux条件表达式例子,Linux的Iptables命令的基本知识(三)-常用匹配条件示例和执行动作...
- 艺术签名python_个性签名设计五十行Python轻松实现
- Mysql主从异常 表被回滚_oracle表回滚到一个指定时间的操作语句 oracle 误删除数据恢复...
- python的结构_Python结构的选择,python,之
- 职业高中计算机英语教案,职高英语shopping教学设计.doc
- 一款不错的开源 Laravel 后台面板/CMS系统 —— LaraAdmin
- Python poetry的使用
- TensorFlow by Google过拟合优化 Machine Learning Foundations: Ep #7 - Image augmentation and overfitting
- 深度置信网络(Deep belief network)matlab初解
- KT148A语音芯片ic的软件参考代码C语言,一线串口
- 微型计算机三部分基本组成,微型计算机的基本组成-电脑自学网
- archlinux 触摸板设置
- 公众号管理系统 v1.0.0
- linux中创建目录树,linux怎样创建目录树
- 基于动态时间规整(DTW)的孤立字语音识别
- csp-s模拟测试49(9.22)养花(分块/主席树)·折射(神仙DP)·画作