no14-构建企业级应用-权限安全框架shiro学习
分类: springboot 专栏: springboot学习 标签: shiro学习
2023-04-07 14:43:10 1036浏览
权限认证基础知识
- 1.什么是认证?
进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说
明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账
号和密码登录微信的过程就是认证。
系统为什么要认证?
认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
认证: 用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份
信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登
录,手机短信登录,指纹认证等方式。
- 2.什么是会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保
持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
- 3.什么是授权
是用户认证通过后根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,否则拒接访问
- 4.授权的数据模型(表设计)
常见的权限数据模型设计(这里直接改成菜单的那种形式)
RBAC
业界通常基于RBAC实现授权
- 1.基于角色的访问控制
role based access control 是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等。
根据上图中的判断逻辑,授权代码可表示如下:
if(主体. hasRole("总经理角色id")){ 查询工资 }
如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是总经理或部门经理" ,修改代码如下:
if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id")){ 查询工资 }
根据上边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可移植性差。
- 2.基于资源的访问控制
resource based access control 按资源(或权限)进行授权,比如:用户必须具有查询工资权限才可以查询员工工资信息等
根据上图中的判断,授权代码可以表示为:
if(主体hasPermission("查询工资权限标识")){ 查询工资 }
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统可扩展性强。
shiro安全框架
核心架构图
Subject: 主体,可以是任何可以与应用交互的“用户”;
SecurityManager: 相当于SpringMVC中的DispatcherServlet,是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator: 认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer: 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm: 可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
SessionManager: 如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
SessionDAO: DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
CacheManager: 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography: 密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
shiro认证的关键对象
- Subject:主体:访问系统的用户,主体可以是用户、程序等,进行认证的都成为主体
- Principal:身份信息:是主体( subject )进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等, 一个主体可以有多个身份,但是必须有一个主身份( Primary Principal )。
- credential:凭证信息:只有主体自己知道的安全信息,如密码、证书等。
认证流程
授权流程
登录业务流程
springboot整合shiro流程
pom加依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.5.3</version> </dependency>
shiro配置类
常用过滤器
- authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
- 需要认证登录才能访问
- user:org.apache.shiro.web.filter.authc.UserFilter
- 用户拦截器,表示必须存在用户
- anon:org.apache.shiro.web.filter.authc.AnonymousFilter
- 匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
- roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
- 角色授权拦截器,验证用户是或否拥有角色。
- 参数可写多个,表示某些角色才能通过,当有多个参数时必须每个参数都通过才算通过
- perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
- 权限授权拦截器,验证用户是否拥有权限
- 参数可写多个,表示需要某些权限才能通过,当有多个参数时必须每个参数都通过才算可以
- authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
- httpBasic 身份验证拦截器。
- logout:org.apache.shiro.web.filter.authc.LogoutFilter
- 退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl()设置的 url
- port:org.apache.shiro.web.filter.authz.PortFilter
- 端口拦截器, 可通过的端口。
- ssl:org.apache.shiro.web.filter.authz.SslFilter
- ssl拦截器,只有请求协议是https才能通过。
@Configuration public class MyShiroConfig { // 将自己的验证方式加入容器 @Bean public GongfuRealm gongfuRealm() { GongfuRealm gongfuRealm = new GongfuRealm(); return gongfuRealm; } // 权限管理,配置主要是Realm的管理认证 @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(gongfuRealm()); return securityManager; } // Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterMap = new LinkedHashMap<>(); //shiro中有一些内置的过滤器 //设置白名单 filterMap.put("/","anon");//anon就是允许匿名访问 filterMap.put("/toLogin","anon");//anon就是允许匿名访问 filterMap.put("/login","anon");//anon就是允许匿名访问 // 剩下的资源对所有用户认证 filterMap.put("/**", "authc"); // 登录 // shiroFilterFactoryBean.setLoginUrl("/login"); // 首页 // shiroFilterFactoryBean.setSuccessUrl("/"); // 错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } }
controller层登录接口
@PostMapping("/login") public String login(@RequestParam("uname") String username, @RequestParam("pwd") String password, HttpSession session){ //获取当前的Subject Subject currentUser = SecurityUtils.getSubject(); //测试当前用户是否已经被认证(即是否已经登录) if (!currentUser.isAuthenticated()) { //将用户名与密码封装为UsernamePasswordToken对象 UsernamePasswordToken token = new UsernamePasswordToken(username, password); //参考文章https://www.cnblogs.com/shineman-zhang/articles/16673144.html //https://blog.csdn.net/qq_36761831/article/details/100579407 token.setRememberMe(true);//记录用户 研究下自动登录问题-浏览器关闭后再打开还是处于登录状态节省时间 try { currentUser.login(token);//调用Subject的login方法执行登录 } catch (AuthenticationException e) {//所有认证时异常的父类 System.out.println("登录失败:"+e.getMessage()); } } return "redirect:/"; } //注销 @RequestMapping("/logout") public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/"; }
认证realm
public class GongfuRealm extends AuthorizingRealm { /** * 认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证了"); String username = (String) authenticationToken.getPrincipal(); if("root".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("root","123456",this.getName()); return simpleAuthenticationInfo; } if("admin".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("admin","123456",this.getName()); return simpleAuthenticationInfo; } if("wang".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("wang","123456",this.getName()); return simpleAuthenticationInfo; } return null; } /** * 授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } }
shiro授权
- controller部分加 @RequiresRoles("")
/** * level1页面映射 * @param path * @return */ @RequiresRoles("ROLE_vip1") @GetMapping("/level1/{path}") public String level1(@PathVariable("path")String path) { return PREFIX+"level1/"+path; } /** * level2页面映射 * @param path * @return */ @RequiresRoles("ROLE_vip2") @GetMapping("/level2/{path}") public String level2(@PathVariable("path")String path) { return PREFIX+"level2/"+path; } /** * level3页面映射 * @param path * @return */ @RequiresRoles("ROLE_vip3") @GetMapping("/level3/{path}") public String level3(@PathVariable("path")String path) { return PREFIX+"level3/"+path; }
- 授权realm
/** * 授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("授权了"); String username = (String) principalCollection.getPrimaryPrincipal(); if("root".equals(username)){ SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(Arrays.asList("ROLE_vip1","ROLE_vip2","ROLE_vip3")); return authorizationInfo; } if("admin".equals(username)){ SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(Arrays.asList("ROLE_vip1","ROLE_vip2")); return authorizationInfo; } if("wang".equals(username)){ SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(Arrays.asList("ROLE_vip1")); return authorizationInfo; } return null; }
shiro中授权编程实现方式
- 1.编程式
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole( "admin" )) { //有权限 }else { //无权限 }
- 2.注解式
@RequiresRoles("admin") public void hello(){ //有权限 }
- 3.标签式
JSP/GSP标签:在JSP/GSP页面通过相应的标签完成:
< shiro:hasRole name= " admin" > <!-有权限--> </shiro:hasRole>
注意: Thymeleaf中使用shiro需要额外集成
密码加密加盐
- 1.测试类生成的密码密文
@Test void contextLoads() { //生成md5加密加盐 String password = "123456"; String root = new Md5Hash(password, "root",1024).toHex(); String admin = new Md5Hash(password, "admin",1024).toHex(); String wang = new Md5Hash(password, "wang",1024).toHex(); System.out.println("root:"+root); System.out.println("admin:"+admin); System.out.println("wang:"+wang); }
- 2.配置shiro修改
@Bean public GongfuRealm gongfuRealm() { GongfuRealm gongfuRealm = new GongfuRealm(); HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("MD5"); credentialsMatcher.setHashIterations(1024);//加密1024次 gongfuRealm.setCredentialsMatcher(credentialsMatcher); return gongfuRealm; }
- 3.修改认证realm
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证了"); String username = (String) authenticationToken.getPrincipal(); if("root".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("root","b1ba853525d0f30afe59d2d005aad96c", ByteSource.Util.bytes("root"),this.getName()); return simpleAuthenticationInfo; } if("admin".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("admin","038bdaf98f2037b31f1e75b5b4c9b26e",ByteSource.Util.bytes("admin"),this.getName()); return simpleAuthenticationInfo; } if("wang".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("wang","a58ce7f92558fffeb9ff9ad0a7d44bc1", ByteSource.Util.bytes("wang"),this.getName()); return simpleAuthenticationInfo; } return null; }
关联数据库
@Autowired UserInfoService userInfoService; /** * 认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证了"); String username = (String) authenticationToken.getPrincipal(); UserInfo userInfoByName = userInfoService.getUserInfoByName(username); if(userInfoByName == null){ return null; } SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userInfoByName.getUname(),userInfoByName.getPwd(), ByteSource.Util.bytes(userInfoByName.getSalt()),this.getName()); return simpleAuthenticationInfo; /* if("root".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("root","b1ba853525d0f30afe59d2d005aad96c", ByteSource.Util.bytes("root"),this.getName()); return simpleAuthenticationInfo; } if("admin".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("admin","038bdaf98f2037b31f1e75b5b4c9b26e",ByteSource.Util.bytes("admin"),this.getName()); return simpleAuthenticationInfo; } if("wang".equals(username)){ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("wang","a58ce7f92558fffeb9ff9ad0a7d44bc1", ByteSource.Util.bytes("wang"),this.getName()); return simpleAuthenticationInfo; } return null;*/ } /** * 授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("授权了"); String username = (String) principalCollection.getPrimaryPrincipal(); UserInfo userinfo = userInfoService.getUserInfoByName(username); if(userinfo!=null && userinfo.getRoles()!=null){ List<Role> roles = userinfo.getRoles(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); if(!CollectionUtils.isEmpty(roles)){ for (Role role : roles){ authorizationInfo.addRole(role.getRoleName()); } } return authorizationInfo; } return null; /* if("root".equals(username)){ SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(Arrays.asList("ROLE_vip1","ROLE_vip2","ROLE_vip3")); return authorizationInfo; } if("admin".equals(username)){ SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(Arrays.asList("ROLE_vip1","ROLE_vip2")); return authorizationInfo; } if("admin".equals(username)){ SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(Arrays.asList("ROLE_vip1")); return authorizationInfo; } return null;*/ } }
页面动态化
- 1.先引入标签依赖
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
- 2.页面代码
引入标签:xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
主要用到的标签:
shiro:hasRole
shiro:notAuthenticated=""
shiro:authenticated=""
<html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1 align="center">欢迎光临武林秘籍管理系统</h1> <h2 align="center" shiro:notAuthenticated="" >游客您好,如果想查看武林秘籍 <a th:href="@{/toLogin}">请登录</a></h2> <!--登陆后--> <div shiro:authenticated="" > <h2><span shiro:principal="" ></span>,您好,您的角色有: <span th:each="role:${session.user.roles}">[[${role.roleName}]] </span></h2> <form th:action="@{/logout}" method="post"> <input type="submit" value="注销"/> </form> </div> <hr> <div shiro:hasRole="ROLE_vip1"> <h3>普通武功秘籍</h3> <ul> <li><a th:href="@{/level1/1}">罗汉拳</a></li> <li><a th:href="@{/level1/2}">武当长拳</a></li> <li><a th:href="@{/level1/3}">全真剑法</a></li> </ul> </div> <div shiro:hasRole="ROLE_vip2"> <h3>高级武功秘籍</h3> <ul> <li><a th:href="@{/level2/1}">太极拳</a></li> <li><a th:href="@{/level2/2}">七伤拳</a></li> <li><a th:href="@{/level2/3}">梯云纵</a></li> </ul> </div> <div shiro:hasRole="ROLE_vip3" > <h3>绝世武功秘籍</h3> <ul> <li><a th:href="@{/level3/1}">葵花宝典</a></li> <li><a th:href="@{/level3/2}">龟派气功</a></li> <li><a th:href="@{/level3/3}">独孤九剑</a></li> </ul> </div> </body> </html>
- 3.添加方言配置
这个必须加,不然前端页面的标签不会生效的
/** * 添加方言 * @return */ @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); }
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论