有手就行的 Spring Boot 集成 Shiro
前言
Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
clipboard.pngAuthentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
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相关推荐
- 解决Spring Boot集成Shiro,配置类使用Autowired无法注入Bean问题
为什么80%的码农都做不了架构师?>>> 如题,最近使用spring boot集成shiro,在shiroFilter要使用数据库动态给URL赋权限的时候,发现 @Autowi ...
- Spring Boot教程(十六):Spring Boot集成shiro
Apache Shiro™是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理.借助Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到 ...
- Spring Boot集成Hazelcast实现集群与分布式内存缓存
2019独角兽企业重金招聘Python工程师标准>>> Hazelcast是Hazelcast公司开源的一款分布式内存数据库产品,提供弹性可扩展.高性能的分布式内存计算.并通过提供诸 ...
- Spring Boot 集成 Mybatis 实现双数据源
转载自 Spring Boot 集成 Mybatis 实现双数据源 这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源 ...
- Spring Boot 集成 Elasticsearch 实战
今天讲解下如何使用 Spring Boot 结合 ES. 可以在 ES 官方文档中发现,ES 为 Java REST Client 提供了两种方式的 Client:Java Low Level Cli ...
- Spring Boot集成Swagger
Spring Boot集成Swagger @(Swagger)[swagger, springfox, springboot] Spring Boot集成Swagger 前言 基本概述 案例 引入依赖 ...
- spring boot—集成log4j2日志框架
文章目录 市场上的日志框架 spring boot日志框架关系 移除默认日志框架 切换为log4j2日志框架 市场上的日志框架 1)日志门面最常用的是slf4j 2)日志实现最常用的是logb ...
- Linux 安装Redis-6.2.5,配置及使用(RDB与AOF持久化、sentinel机制、主从复制、Spring Boot 集成 Redis)
CentOS 7 安装Redis-6.2.5版本 Redis采用的是基于内存的单进程 单线程模型 的KV数据库,由C语言编写.官方提供的数据是可以达到100000+的qps 应用场景: 令牌(Toke ...
- Spring Boot集成阿里云视频点播服务的过程记录
阿里云视频点播 效果预览 视频点播 视频点播概述 功能 优势 流程 环境准备 开通视频点播 创建RAM用户并授权 上传SDK 上传流程 下载上传SDK 安装上传SDK 集成Java上传SDK 异常说明 ...
最新文章
- DVWA默认用户名密码
- 基于mysql传统复制模式转为GTID模式 mysql 5.7版本
- 记录一次异常 出现不支持的 SQL92 标记: 70
- RHEL5.1NFS+NIS+Authconfig+Autofs实现自动挂载NIS用户主目录
- 云计算实战系列一(走进linux)
- UnityShader中的Queue
- 很感谢你能来,不遗憾你离开(好文章)
- LINGO如何使用教育邮箱免费激活
- ExtJS教程(1)---初窥ExtJs
- 6603网狐棋牌搭建视频教程
- oracle数据库写文件,Oracle对操作系统文件的读写操作
- 因子分析的实用介绍:探索性因子分析
- Pr 与音频相关的调整方法
- win10无线网卡启动服务器,win10系统无线网卡被禁用怎么办?win10开启无线网卡的方法...
- SecureCRT复制粘贴快捷设置
- 学会配色-色彩配色表
- 网络运维系列:网络出口IP地址查询
- SYSLINUX中文介绍
- Unifying Task-oriented Knowledge Graph Learning and Recommendation
- 产品经理与工程师的换位思考
热门文章
- C++常见面试题知识点
- 10 simple Tips to Avoid Violating Google Adsense TOS Read more: 10 simple Tips to avoid violating G
- jquery+bootstrap 创建日历表格
- 应用计算机在金融系统,计算机在金融中的应用
- 一行代码解决vue数据量大卡顿问题
- 华大(现在改名小华半导体)芯片启动文件详细讲解
- Chrome上最好用的广告拦截插件:AdBlock
- spring boot 微服务入门
- 《悟透JavaScript》诞生历程精美配乐视频
- android学习心得【安卓入门一】