Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 SpringSecurity,可能没有 Spring Security 做的功能强大,但是在实际工作时我们可能并不需要那么复杂的东西,所以使用小而简单的Shiro 就足够了。 什么是身份认证? 什么是授权? 什么是会话管理? 什么是加密? 什么是主体(subject)? 如果我们写一个最简单的Shiro应用,那么大概流程应该是—— 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。 Subject SecurityManager Authenticator Cache Manager Session Manager Realm Authorizer Cryptography Concurrency SessionDAO 快速启动测试 a、使用jdbc.ini文件 快速启动 b、使用Java配置值类 导入shiro依赖: 这里使用的是shiro内置的realm 步骤: 是不是感觉一头雾水 别急下面列出对应功能 这些过滤器分为两组,一组是认证过滤器,一组是授权过滤器。 权限校验的两种方式 shiro权限控制注解和编程方式讲解 用户与程序的链接,程序可以根据session来区分不同的用户。 会话管理器管理所有的subject的操作,是shiro的核心 4.1、登录时,我们输入的是明文,但是realm使用了一层MD5加密,所以你的数据库里存储的密码应该要是密文才能校验通过,否则就会出现下面错误
关于shiro的学习推荐官网:
链接地址: https://shiro.apache.org/ 前言:
一、ACL和RBAC
访问控制列表 以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩
优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系统权限设计, 直接给用户加权限
基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来 缺点:开发对比ACL相对复杂
例子:基于RBAC模型的权限验证框架与应用 Apache Shiro、spring Security BAT企业
ACL,一般是对报表系统,阿里的ODPS二、shiro中的一些基础概念和专业名词(重要)
1、基础概念:
Authentication,身份证认证,一般就是登录
Authorization,给用户分配角色或者访问某些资源的权限
Session Management, 用户的会话管理员,多数情况下是web session
Cryptography, 数据加解密,比如密码加解密等
主体,可以看到主体可以是任何可以与应用交互的“用户”;2、shiro实现原理流程图解:
在这个流程里面,我们可以看到API 核心就是 Subject,其中各个部分的解释与作用——
要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 /权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。3、架构图;
4、框架专业名词解读
我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源
安全管理器,Subject的认证和授权都要在安全管理器下进行
认证器,主要负责Subject的认证
缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能
会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
数据域,Shiro和安全数据的连接器,好比jdbc连接数据库; 通过realm获取认证授权相关信息
授权器,主要负责Subject的授权, 控制subject拥有的角色或者权限
加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的api
shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;5、安全数据源realm
5.1、shiro自带的默认的realm
默认自带的realm:idae查看realm继承关系,有默认实现和自定义继承的realm 两个概念 5.2、shiro数据源使用.ini配置文件
ini配置文件内容:# 格式 name=password,role1,role2,..roleN [users] # user 'root' with password 'secret' and the 'admin' role, jack = 456, user # user 'guest' with the password 'guest' and the 'guest' role xdcalss = 123, root # 格式 role=permission1,permission2...permissionN 也可以用通配符 # 下面配置user的权限为所有video:find,video:buy,如果需要配置video全部操作crud 则 user = video:* [roles] user = video:find,video:buy # 'admin' role has all permissions, indicated by the wildcard '*' admin = *
@Test public void test(){ Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); //设置token 模拟用户输入账号密码 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack","456"); subject.login(usernamePasswordToken); System.out.println(subject.isAuthenticated()); subject.hasRole("admin"); System.out.println(subject.getPrincipal()); }
5.3、shiro数据源realm使用jdbc方式注入
#注意 文件格式必须为ini,编码为ANSI #声明Realm,指定realm类型 jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm #配置数据源 #dataSource=com.mchange.v2.c3p0.ComboPooledDataSource dataSource=com.alibaba.druid.pool.DruidDataSource # mysql-connector-java 5 用的驱动url是com.mysql.jdbc.Driver,mysql-connector-java6以后用的是 com.mysql.cj.jdbc.Driver dataSource.driverClassName=com.mysql.cj.jdbc.Driver #避免安全警告 dataSource.url=jdbc:mysql://localhost:3306/myshiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false dataSource.username=wyy dataSource.password=xxx #指定数据源 jdbcRealm.dataSource=$dataSource #开启查找权限, 默认是false,不会去查找角色对应的权限,坑!!!!! jdbcRealm.permissionsLookupEnabled=true #指定SecurityManager的Realms实现,设置realms,可以有多个,用逗号隔开 securityManager.realms=$jdbcRealm
@Test public void test2(){ Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbcrealm.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); //设置token 模拟用户输入账号密码 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack","123"); subject.login(usernamePasswordToken); System.out.println(subject.isAuthenticated()); subject.hasRole("admin"); System.out.println(subject.getPrincipal()); }
@Test public void test1(){ DefaultSecurityManager securityManager = new DefaultSecurityManager(); DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.mysql.cj.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/myshiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false"); ds.setUsername("wyy"); ds.setPassword("xxx"); JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setPermissionsLookupEnabled(true); jdbcRealm.setDataSource(ds); securityManager.setRealm(jdbcRealm); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); //设置token 模拟用户输入账号密码 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack","123"); subject.login(usernamePasswordToken); System.out.println(subject.isAuthenticated()); subject.hasRole("admin"); System.out.println(subject.getPrincipal()); }
三、整合boot快速启动
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
/** * @author wyy * @version 1.0 * @date 2020/3/27 10:11 * @description **/ public class QuickStart_shiro { private SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); private DefaultSecurityManager securityManager = new DefaultSecurityManager(); @Before public void init(){ //添加账户 simpleAccountRealm.addAccount("zhangsan","123"); //设置realm 构建 环境 securityManager.setRealm(simpleAccountRealm); } @Test public void test(){ //设置上下文 SecurityUtils.setSecurityManager(securityManager); //当前操作主体 Subject subject = SecurityUtils.getSubject(); //设置token 模拟用户输入账号密码 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan","1234"); subject.login(usernamePasswordToken); Boolean isAccess = subject.isAuthenticated(); System.out.println(isAccess); } }
四、shiro自定义realm(重要)
1、重写方法介绍:
2、一些被用到的对象介绍:
UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential 继承树: UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken SimpleAuthorizationInfo:代表用户角色权限信息 SimpleAuthenticationInfo :代表该用户的认证信息
3、自定义realm
/** * @author wyy * @version 1.0 * @date 2020/4/7 14:38 * @description **/ public class CustomRealm extends AuthorizingRealm { //创建模拟用户 private final Map<String,String> userInfoMap = new HashMap<>(); { userInfoMap.put("jack","123"); userInfoMap.put("admin","123"); } //创建模拟权限 private final Map<String,Set<String>> permissionMap = new HashMap<>(); { Set<String> set1 = new HashSet<>(); Set<String> set2 = new HashSet<>(); set1.add("video:find"); set1.add("video:buy"); set2.add("video:add"); permissionMap.put("jack",set1); permissionMap.put("admin",set2); } //创建模拟角色 private final Map<String,Set<String>> roleMap = new HashMap<>(); { Set<String> set1 = new HashSet<>(); Set<String> set2 = new HashSet<>(); set1.add("role1"); set1.add("role2"); set2.add("root"); roleMap.put("jack",set1); roleMap.put("admin",set2); } //进行权限校验的时候会调用: doGetAuthorizationInfo @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("权限:doGetAuthorizationInfo"); String name = (String)principalCollection.getPrimaryPrincipal(); Set<String> permission = getPermissionsFromDB(name); Set<String> roles = getRolesByNameFromDB(name); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRoles(roles); simpleAuthorizationInfo.addStringPermissions(permission); return simpleAuthorizationInfo; } private Set<String> getRolesByNameFromDB(String name) { return roleMap.get(name); } private Set<String> getPermissionsFromDB(String name) { return permissionMap.get(name); } //当用户登陆的时候会调用 doGetAuthenticationInfo @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证:doGetAuthenticationInfo"); String name = (String)authenticationToken.getPrincipal(); String pwd = getPwdByNameFromDB(name); if(null == pwd || "".equals(pwd)){ return null; } //pwd 可能需要一个加盐操作 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name,pwd,this.getName()); return simpleAuthenticationInfo; } private String getPwdByNameFromDB(String name) { return userInfoMap.get(name); } }
4、自定义realm实例化
/** * @author wyy * @version 1.0 * @date 2020/4/7 14:39 * @description **/ public class QuickStart_shiro2 { private CustomRealm simpleAccountRealm = new CustomRealm(); private DefaultSecurityManager securityManager = new DefaultSecurityManager(); @Before public void init(){ //设置realm 构建 环境 securityManager.setRealm(simpleAccountRealm); //设置上下文 SecurityUtils.setSecurityManager(securityManager); } @Test public void test(){ //当前操作主体 Subject subject = SecurityUtils.getSubject(); //设置token 模拟用户输入账号密码 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack","123"); subject.login(usernamePasswordToken); Boolean isAccess = subject.isAuthenticated(); subject.checkPermission("video:find"); subject.checkPermission("video:buy"); subject.hasRole("root"); System.out.println(isAccess); } }
五、shiro内置过滤器Fileter讲解(重要)
1、DefaultFilter 及其内部的枚举类
ssl :org.apache.shiro.web.filter.authz.SslFilter user :org.apache.shiro.web.filter.authz.UserFilter anon :org.apache.shiro.web.filter.authc.AnonymousFilter port :org.apache.shiro.web.filter.authz.PortFilter rest :org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter authc :org.apache.shiro.web.filter.authc.FormAuthenticationFilter perms :org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter roles :org.apache.shiro.web.filter.authz.RolesAuthorizationFilter logout :org.apache.shiro.web.filter.authc.LogoutFilter authcBasic :org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter noSessionCreation :org.apache.shiro.web.filter.session.NoSessionCreationFilter
//匿名访问 游客访问路径 filterChainDefinitionMap.put("/pub/**", "anon"); //退出过滤器 filterChainDefinitionMap.put("/user/logout", "logout"); //登录用户才能访问 filterChainDefinitionMap.put("/author/**", "authc"); filterChainDefinitionMap.put("/author/**", "authcBasic"); //只有admin管理员才能访问 filterChainDefinitionMap.put("/admin/**", "roles[admin]"); //只有视频修改权限的才能访问 filterChainDefinitionMap.put("/goods/video/update", "perms[video_update]"); //authc : url 必须通过认证才能访问 //anon : url可以匿名访问 filterChainDefinitionMap.put("/**", "authc"); port:org.apache.shiro.web.filter.authz.PortFilter 端口拦截器, 可通过的端口。 ssl:org.apache.shiro.web.filter.authz.SslFilter ssl拦截器,只有请求协议是https才能通过
1、注解方式 @RequiresRoles(value={"admin", "editor"}, logical= Logical.AND) 需要角色 admin 和 editor两个角色 AND表示两个同时成立 @RequiresPermissions (value={"user:add", "user:del"}, logical= Logical.OR) 需要权限 user:add 或 user:del权限其中一个,OR是或的意思。 @RequiresAuthentication 已经授过权,调用Subject.isAuthenticated()返回true @RequiresUser 身份验证或者通过记 住我登录的
2、编码形式 Subject subject = SecurityUtils.getSubject(); //基于角色判断 if(subject.hasRole(“admin”)) { //有角色,有权限 } else { //无角色,无权限 } //或者权限判断 if(subject.isPermitted("/user/add")){ //有权限 }else{ //无权限 }
2、shiroFilterFactoryBean配置编写(非常重要)
1、配置流程与思路
2、过滤器shiroFilterFactoryBean的实现 Java配置类的书写
/** * @author wyy * @version 1.0 * @Classname ShiroConfig * @date 2020/4/16 10:56 * @description **/ @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("ShiroFilterFactoryBean.shiroFilter() 执行了"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //这里设置了登录接口 如果访问了某个接口没有登录 就会调用这个接口(如果不是前后端分离就返回页面) shiroFilterFactoryBean.setLoginUrl("/pub/need_login"); //如果是前后端分离则不需要这个 //shiroFilterFactoryBean.setSuccessUrl(); //这个是用户登录了 但没有权限 就会调用这个接口 shiroFilterFactoryBean.setUnauthorizedUrl("/pub/need_permission"); /** *拦截器路径 坑1:部分路径无法拦截,拦截效果时有时无,因为使用了hashMap 是无序的应该使用linkedHashMap *原因在于过滤链执行顺序是顺序执行所以需要使用有序的linkedHashMap **/ Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //匿名访问 游客访问路径 filterChainDefinitionMap.put("/pub/**", "anon"); //退出过滤器 filterChainDefinitionMap.put("/user/logout", "logout"); //登录用户才能访问 filterChainDefinitionMap.put("/author/**", "authc"); //只有管理员才能访问 filterChainDefinitionMap.put("/admin/**", "roles[admin]"); //视频修改权限 filterChainDefinitionMap.put("/goods/video/update","perms[video_update]"); //坑2:过滤链是顺序执行的,从上而下,一般来讲/** 是放到最下面的 //authc : url 必须通过认证才能访问 //anon : url可以匿名访问 filterChainDefinitionMap.put("/**","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customRealm()); securityManager.setSessionManager(sessionManager()); return securityManager; } //自定义realm注入 @Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return customRealm; } //自定义sessionmanager注入 @Bean public SessionManager sessionManager() { //使用自定义sessionmanager 具体写法看 六—3 CustomSessionManager customSessionManager = new CustomSessionManager(); return customSessionManager; } //加密方式 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //设置散列算法 :这里设置的MD5 credentialsMatcher.setHashAlgorithmName("MD5"); //设置多重加密算法 :这里设置的是2次加密(mad5(md5(xxx))) credentialsMatcher.setHashIterations(2); return credentialsMatcher; } }
六、shiro缓存模块
1、什么是会话session?
2、什么是会话管理器 sessionmanager?
核心方法: //开启一个session Session start(SessionContext context); //指定Key获取session Session getSession(SessionKey key)
shiro中的会话管理器有多个实现 SessionDao 会话存储/持久化 SessionDAO AbstractSessionDAO CachingSessionDAO EnterpriseCacheSessionDAO MemorySessionDAO 核心方法 //创建 Serializable create(Session session); //获取 Session readSession(Serializable sessionId) throws UnknownSessionException; //更新 void update(Session session) //删除,会话过期时会调用 void delete(Session session); //获取活跃的session Collection<Session> getActiveSessions();
3、自定义sessionManager验证
/** * @author wyy * @version 1.0 * @Classname CustomSessionManager * @date 2020/4/16 13:35 * @description **/ public class CustomSessionManager extends DefaultWebSessionManager { private final static String AUTHORIZATION = "token"; public CustomSessionManager(){ super(); } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION); if(null != sessionId ){ request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return sessionId; }else{ return super.getSessionId(request,response); } } }
七、api验证
/** * @author wyy * @version 1.0 * @Classname PublicController * @date 2020/4/20 10:03 * @description **/ @RestController @RequestMapping(name = "公共接口",value = "/pub") public class PublicController { @RequestMapping(name = "设置的未登录拦截接口",value = "/need_login") public JsonData needLogin(){ return JsonData.buildSuccess(-2,"请先登录"); } @RequestMapping(name = "设置登录但未授权的用户拦截接口",value = "/need_permission") public JsonData needPermission(){ return JsonData.buildSuccess(-3,"您没有权限,请联系管理员!"); } @RequestMapping(name = "初始化界面",value = "/index") public JsonData index(){ List<String> goods = new ArrayList<>(); goods.add("空调"); goods.add("水壶"); goods.add("电冰箱"); goods.add("热水器"); goods.add("烤箱"); return JsonData.buildSuccess(goods); } @RequestMapping(name = "登录",value = "login") public JsonData login(@RequestBody UserBO userBO, HttpServletRequest request, HttpServletResponse response){ Subject subject = SecurityUtils.getSubject(); Map<String,Object> map = new HashMap<>(16); try { UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userBO.getName(), userBO.getPwd()); subject.login(usernamePasswordToken); map.put("token",subject.getSession().getId()); }catch (Exception e){ map.put("msg","账号或密码不存在!"); } return JsonData.buildSuccess(map); } }
1、游客访问接口:不需要登录就可以访问
@RestController @RequestMapping(value = "author") public class OrderController { @RequestMapping(value = "listOrder") public JsonData ListOrder(){ Map<String,String> map = new HashMap<>(16); map.put("springboot基础","19.00元"); map.put("redis基础","29.00元"); map.put("activeMq基础","13.00元"); return JsonData.buildSuccess(map); } }
2、author:需要用户登录才能访问接口
@RestController @RequestMapping(value = "/goods/video") public class VideoController { @RequestMapping(value = "update") public JsonData videoUpdate(){ return JsonData.buildSuccess(); } }
3、需要有video_update 权限的用户才能访问
这里只有Jack有更新权限所以John是没有权限访问该接口的/** * @author wyy * @version 1.0 * @date 2020/4/14 9:46 * @description **/ @RestController @RequestMapping(name = "用户模块", value = "admin") public class UserController { @RequestMapping(name = "根据id获取角色所有权限",value = "/getPermissionById") public List<Role> getRoles(@RequestParam(value = "id") Integer id){ return userService.getRoles(id); } }
4、注意点:
当然上面的登录例子里面进行了try catch 操作所以不会抛错
4.2、登录获取的token并非永久,如果不设置过期时间shiro默认30分钟,这里我们在sessionmanager里面配置了session过期时间18s
4.3、前端传的凭证名称是由后端sessionmanager规定
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算