前言

  Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
    clipboard.png

  • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;

  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;

  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;

  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;

  • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Spring Boot 集成 Shiro

pom.xml

        <!--Shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>${shiro.version}</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro.version}</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>${shiro.version}</version></dependency>

ShiroConfig

/*** @ClassName ShiroConfig* @Author zwy* @Data 1/4/2021 下午4:31*/
@Configuration
public class ShiroConfig {@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setFilterChainDefinitionMap((Map<String, String>) new HashMap<>().put("jwtFilter",new JwtFilter()));// 添加 jwt 专用过滤器,拦截除 /login 和 /logout 外的请求Map<String, Filter> filterMap = new LinkedHashMap<>();filterMap.put("jwtFilter", new JwtFilter());shiroFilterFactoryBean.setFilters(filterMap);Map<String, String> linkedHashMap = new LinkedHashMap<>();linkedHashMap.put("/doc.html","anon");//设置不拦截druidlinkedHashMap.put("/druid/**","anon");//Swagger的所有请求的资源和请求的地址都不需要拦截linkedHashMap.put("/swagger/**","anon");linkedHashMap.put("/v2/api-docs","anon");linkedHashMap.put("/swagger-ui.html","anon");linkedHashMap.put("/swagger-resources/**","anon");linkedHashMap.put("/webjars/**","anon");linkedHashMap.put("/csrf","anon");linkedHashMap.put("/login","anon");linkedHashMap.put("/captcha.jpg","anon");linkedHashMap.put("/email","anon");linkedHashMap.put("/register","anon");linkedHashMap.put("/**","jwtFilter,authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(linkedHashMap);shiroFilterFactoryBean.setSecurityManager(securityManager());return shiroFilterFactoryBean;}@Beanpublic SecurityManager securityManager(){DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();Set<Realm> set = new HashSet<>();set.add(MyRealm());set.add(TokenRealm());defaultWebSecurityManager.setRealms(set);return defaultWebSecurityManager;}/*** @param securityManager* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** Token 认证* @return*/@Beanpublic Realm TokenRealm(){TokenRealm shiroRealm = new TokenRealm();return  shiroRealm;}/*** 账号认证* @return*/@Beanpublic Realm MyRealm(){ShiroRealm shiroRealm = new ShiroRealm();shiroRealm.setCredentialsMatcher(credentialsMatcher());return  shiroRealm;}@Beanpublic CredentialsMatcher credentialsMatcher(){HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName(LogicUtil.hashName);hashedCredentialsMatcher.setHashIterations(LogicUtil.hashIterations);return hashedCredentialsMatcher;}}

TokenRealm(token认证)

/*** @author lanys* @Description:* @date 22/6/2021 下午3:00*/
public class TokenRealm extends AuthorizingRealm {@Autowiredprivate SysMenuServer menuServer;@Autowiredprivate SysUserService userService;@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 管理权限* @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//得登录的用户名SysUser username = (SysUser) principals.getPrimaryPrincipal();System.out.println(("username:"+username.getUsername()));Set<String> permissionSet = new HashSet<>();List<String> permissions;if (Constants.SUPER_ADMIN.equals(username)){permissions = menuServer.findPermissionsByRole();if (permissions != null) {for (String permission : permissions) {if (StrUtil.isNotEmpty(permission)) {permissionSet.add(permission);}}}}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermissions(permissionSet);return info;}/*** 管理登录  如果有异常,则令牌无效* @param token* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//得到客户端传过来的令牌String tokenString =(String) token.getCredentials();//得到String principal = (String) token.getPrincipal();SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, principal));//根据令牌得用户名,可以查用户库if (tokenString==null&&sysUser==null){throw new RuntimeException("登录失败");}return new SimpleAuthenticationInfo(sysUser, tokenString, getName());}
}

ShiroRealm(账号密码认证)

/*** @ClassName ShiroRealm* @Author zwy* @Data 1/4/2021 下午4:37*/
public class ShiroRealm extends AuthorizingRealm {@Autowiredprivate SysUserService userService;@Autowiredprivate SysMenuServer menuServer;/*** 授权* @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {System.out.println("权限授权");Set<String> permissionSet = new HashSet<>();List<String> permissions;SysUser userName = (SysUser) principals.getPrimaryPrincipal();if (Constants.SUPER_ADMIN.equals(userName.getUsername())){permissions = menuServer.findPermissionsByRole();if (permissions != null) {for (String permission : permissions) {if (StrUtil.isNotEmpty(permission)) {permissionSet.add(permission);}}}}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermissions(permissionSet);return info;}/*** 认证* @param token* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String principal = (String) token.getPrincipal();LambdaQueryWrapper<SysUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(SysUser::getUsername,principal);SysUser user = userService.getOne(lambdaQueryWrapper);SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),new Md5Hash(user.getSalt()),getName());return simpleAuthenticationInfo;}@Overridepublic void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {super.setCredentialsMatcher(credentialsMatcher);}}

JwtToken(token认证实体)

/*** @author lanys* @Description:* @date 21/6/2021 下午12:52*/public class JwtToken implements AuthenticationToken {private static final long serialVersionUID = 1L;// 加密后的 JWT token串private String token;private String userName;public JwtToken(String token) {this.token = token;this.userName = TokenUtil.getClaimFiled(token, Constants.jwtTokenSecret);}@Overridepublic Object getPrincipal() {return this.userName;}@Overridepublic Object getCredentials() {return this.token;}
}

JwtFilter(token过滤器)

/*** @author lanys* @Description:* @date 21/6/2021 上午11:42*/
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {private String getToken(ServletRequest request, ServletResponse response) {HttpServletRequest servletRequest = (HttpServletRequest) request;String authorization = servletRequest.getHeader("Authorization");if (authorization==null){return null;}String token = authorization.replace("Bearer ", "");return token;}/*** 前置处理*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = WebUtils.toHttp(request);HttpServletResponse httpServletResponse = WebUtils.toHttp(response);// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(HttpStatus.OK.value());return false;}return super.preHandle(request, response);}@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {String token = getToken(request, response);System.out.println(((HttpServletRequest) request).getHeader("Authorization"));//判断请求的请求头是否带上 "Token"if (token != null) {//如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确try {executeLogin(request, response);return true;} catch (AuthenticationException e) {//token 错误return false;} catch (Exception e) {return false;}}//如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 truereturn true;}@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {String token = getToken(request, response);if (token != null && TokenUtil.isTokenExpired(token) == true) {try {log.info("提交UserModularRealmAuthenticator决定由哪个realm执行操作...");JwtToken jwtToken = new JwtToken(token);// 提交给realm进行登入,如果错误他会抛出异常并被捕获getSubject(request, response).login(jwtToken);} catch (Exception e) {log.info("捕获到身份认证异常");response.setContentType("application/json;charset=UTF-8");response.getWriter().write(Result.fail("Token失效,请重新登录...").toString());return false;}} else {log.info("Token失效...");response.setContentType("application/json;charset=UTF-8");response.getWriter().write(Result.fail("Token失效,请重新登录...").toString());return false;}// 如果没有抛出异常则代表登入成功,返回truereturn true;}@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {HttpServletRequest servletRequest = (HttpServletRequest) request;String token = servletRequest.getHeader("Authorization");return new JwtToken(token);}
}

LoginController(登录)

 @ApiOperation(value = "登录")@ResponseBody@GetMapping("/login")public Result login(String username, String password,String captcha) {//        ShiroUtil.getKaptcha(Constants.KAPTCHA_SESSION_KEY,captcha);if (StringUtils.isAllBlank(username, password,captcha)) {return Result.fail("数据不能为空");}SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password));String token = TokenUtil.doGenerateToken(SecurityLogic.getName(),SecurityLogic.getPassword());EhcacheMap.getInstance().put(token,token);return Result.success().put(token);}

TokenUtil(生成token&检查是否过期)

/*** @ClassName TokenUtil* @Author zwy* @Data 1/4/2021 下午7:05*/
@Slf4j
public class TokenUtil {/*** 解析* @param token* @return*/public static Claims getClaimFromToken(String token) {return Jwts.parser().setSigningKey(Constants.jwtTokenSecret).parseClaimsJws(token).getBody();}/*** 获得token中的自定义信息,无需secret解密也能获得*/public static String getClaimFiled(String token, String filed) {try {return Jwts.parser().setSigningKey(filed).parseClaimsJws(token).getBody().getSubject();} catch (JWTDecodeException e) {return null;}}/*** 获取用户* @param token* @return*/public static String getName(String token){return Jwts.parser().setSigningKey(Constants.jwtTokenSecret).parseClaimsJws(token).getBody().getSubject();}/*** 判断过期** @param token* @return*/public static Boolean isTokenExpired(String token) {try {final Date expiration = getClaimFromToken(token).getExpiration();log.info("token到期时间:"+expiration);return true;} catch (Exception e) {log.info("token过期");return false;}}/*** 生成** @param username* @return*/public static String doGenerateToken(String username,String password) {final Date createdDate = new Date();//Constants.time * 1000final Date finishDate = new Date(createdDate.getTime() + 86400);return Jwts.builder().setSubject(username).setAudience(password).setIssuedAt(createdDate).setExpiration(finishDate).signWith(SignatureAlgorithm.HS256, Constants.jwtTokenSecret).compact();}}

测试

Token验证

总结

Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
Shiro特点:

  • 易于理解的 Java Security API;
  • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC 等);
  • 对角色的简单的签权(访问控制),也支持细粒度的鉴权;
  • 支持一级缓存,以提升应用程序的性能;
  • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
  • 异构客户端会话访问;
  • 非常简单的加密 API(Shiro加密后的密码是解析不出来的);
  • 不跟任何的框架或者容器捆绑,可以独立运行。

有手就行的 Spring Boot 集成 Shiro相关推荐

  1. 解决Spring Boot集成Shiro,配置类使用Autowired无法注入Bean问题

    为什么80%的码农都做不了架构师?>>>    如题,最近使用spring boot集成shiro,在shiroFilter要使用数据库动态给URL赋权限的时候,发现 @Autowi ...

  2. Spring Boot教程(十六):Spring Boot集成shiro

    Apache Shiro™是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理.借助Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到 ...

  3. Spring Boot集成Hazelcast实现集群与分布式内存缓存

    2019独角兽企业重金招聘Python工程师标准>>> Hazelcast是Hazelcast公司开源的一款分布式内存数据库产品,提供弹性可扩展.高性能的分布式内存计算.并通过提供诸 ...

  4. Spring Boot 集成 Mybatis 实现双数据源

    转载自   Spring Boot 集成 Mybatis 实现双数据源 这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源 ...

  5. Spring Boot 集成 Elasticsearch 实战

    今天讲解下如何使用 Spring Boot 结合 ES. 可以在 ES 官方文档中发现,ES 为 Java REST Client 提供了两种方式的 Client:Java Low Level Cli ...

  6. Spring Boot集成Swagger

    Spring Boot集成Swagger @(Swagger)[swagger, springfox, springboot] Spring Boot集成Swagger 前言 基本概述 案例 引入依赖 ...

  7. spring boot—集成log4j2日志框架

    文章目录 市场上的日志框架 spring boot日志框架关系 移除默认日志框架 切换为log4j2日志框架 市场上的日志框架   1)日志门面最常用的是slf4j   2)日志实现最常用的是logb ...

  8. Linux 安装Redis-6.2.5,配置及使用(RDB与AOF持久化、sentinel机制、主从复制、Spring Boot 集成 Redis)

    CentOS 7 安装Redis-6.2.5版本 Redis采用的是基于内存的单进程 单线程模型 的KV数据库,由C语言编写.官方提供的数据是可以达到100000+的qps 应用场景: 令牌(Toke ...

  9. Spring Boot集成阿里云视频点播服务的过程记录

    阿里云视频点播 效果预览 视频点播 视频点播概述 功能 优势 流程 环境准备 开通视频点播 创建RAM用户并授权 上传SDK 上传流程 下载上传SDK 安装上传SDK 集成Java上传SDK 异常说明 ...

最新文章

  1. DVWA默认用户名密码
  2. 基于mysql传统复制模式转为GTID模式 mysql 5.7版本
  3. 记录一次异常 出现不支持的 SQL92 标记: 70
  4. RHEL5.1NFS+NIS+Authconfig+Autofs实现自动挂载NIS用户主目录
  5. 云计算实战系列一(走进linux)
  6. UnityShader中的Queue
  7. 很感谢你能来,不遗憾你离开(好文章)
  8. LINGO如何使用教育邮箱免费激活
  9. ExtJS教程(1)---初窥ExtJs
  10. 6603网狐棋牌搭建视频教程
  11. oracle数据库写文件,Oracle对操作系统文件的读写操作
  12. 因子分析的实用介绍:探索性因子分析
  13. Pr 与音频相关的调整方法
  14. win10无线网卡启动服务器,win10系统无线网卡被禁用怎么办?win10开启无线网卡的方法...
  15. SecureCRT复制粘贴快捷设置
  16. 学会配色-色彩配色表
  17. 网络运维系列:网络出口IP地址查询
  18. SYSLINUX中文介绍
  19. Unifying Task-oriented Knowledge Graph Learning and Recommendation
  20. 产品经理与工程师的换位思考

热门文章

  1. C++常见面试题知识点
  2. 10 simple Tips to Avoid Violating Google Adsense TOS Read more: 10 simple Tips to avoid violating G
  3. jquery+bootstrap 创建日历表格
  4. 应用计算机在金融系统,计算机在金融中的应用
  5. 一行代码解决vue数据量大卡顿问题
  6. 华大(现在改名小华半导体)芯片启动文件详细讲解
  7. Chrome上最好用的广告拦截插件:AdBlock
  8. spring boot 微服务入门
  9. 《悟透JavaScript》诞生历程精美配乐视频
  10. android学习心得【安卓入门一】