shiro
阿帕奇的安全框架
Apache Shiro是一个java的安全管理框架,可以用在javaEE环境下,也可以用在javaSE环境下。
此前我们学习了很多有关阿帕奇的东西:maven,tomcat,等等
官方号称十分钟就可以入门,
为什么学他?:
(1)spring security 功能完善,学习成本偏高;
(2)shiro 学习成本低,简单的安全框架,基本功能存在(登录认证,权限认证);
(3)spring mvc interceptor(拦截器) 只能做登录认证,不能做权限认证。
他能做什么?
Authentication:身份认证/登录;
Authorization:授权;
Session Manager:会话管理;
Cryptography:加密;
Web Support:**Web支持,可以非常容易的集成到Web环境;
Caching:缓存;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我。
Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro。
shiro架构
Subject:主体;
SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器;
Authrizer:授权器,;
Realm:可以有1个或多个Realm,是安全实体数据源;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;
SessionManager:**Shiro自己的Session来管理主体与应用之间交互的数据;
SessionDAO:**DAO大家都用过,数据访问对象,用于会话的CRUD;同时SessionDao也可以使用Cache进行缓存以提高性能。
CacheManager:缓存控制器,管理如用户、角色、权限等的缓存
Cryptography:密码模块
helloshiro
导入相关依赖:官方依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.6.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
<resources> <resource> <directory>${basedir}/src/main/webapp</directory> </resource> <resource> <directory>${basedir}/src/main/resources</directory> </resource> <resource> <directory>${basedir}/src/main/java</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> <include>**/*.yml</include> </includes> </resource> </resources>
修改后的Quickstart类,
最新版本建议将:
FactorySecurityManager factory = new IniSecurityManagerFactory(classpath:shiro.ini); SecurityManager securityManager = factory.getInstance(); 转换位下面这样 DefaultSecurityManager securityManager = new DefaultSecurityManager(); IniRealm iniRealm = new IniRealm(classpath:shiro.ini); securityManager.setRealm(iniRealm);
下面代码是Quickstart类:
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple Quickstart application showing how to use Shiro's API. * * @since 0.9 RC2 */ public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { // The easiest way to create a Shiro SecurityManager with configured // realms, users, roles and permissions is to use the simple INI config. // We'll do that by using a factory that can ingest a .ini file and // return a SecurityManager instance: // Use the shiro.ini file at the root of the classpath // (file: and url: prefixes load from files and urls respectively): DefaultSecurityManager securityManager = new DefaultSecurityManager(); IniRealm iniRealm = new IniRealm("classpath:shiro.ini"); securityManager.setRealm(iniRealm); // for this simple example quickstart, make the SecurityManager // accessible as a JVM singleton. Most applications wouldn't do this // and instead rely on their container configuration or web.xml for // webapps. That is outside the scope of this simple quickstart, so // we'll just do the bare minimum so you can continue to get a feel // for things. SecurityUtils.setSecurityManager(securityManager); // Now that a simple Shiro environment is set up, let's see what you can do: // get the currently executing user: Subject currentUser = SecurityUtils.getSubject(); // Do some stuff with a Session (no need for a web or EJB container!!!) Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("Retrieved the correct value! [" + value + "]"); } // let's login the current user so we can check against roles and permissions: if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // ... catch more exceptions here (maybe custom ones specific to your application? catch (AuthenticationException ae) { //unexpected condition? error? } } //say who they are: //print their identifying principal (in this case, a username): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //test a role: if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } //test a typed permission (not instance-level) if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //a (very powerful) Instance Level permission: if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } //all done - log out! currentUser.logout(); System.exit(0); } }
三大对象:
Subject
SecurityManage
realm
shiro整个流程
常用过滤器如下
anon:无需认证访问
authc:必须认证了才能访问
user:记住我开启了,才可以用
perms:拥有对某个资源的权限才能访问
role:该资源必须得到角色权限才可以访问
代码实战:
首先是shiroconfig,我们从下往上配置:
首先是创建一个realm类
之后是创建shiroconfig
之后从下往上配置
首先是引入realm类
配置安全管理器
之后设置过滤工厂
package com.hyc.config; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; @Configuration public class shrioconfig { // shirofilterfactoryBean @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); /* * 常用过滤器如下 * anon:无需认证访问 * authc:必须认证了才能访问 * user:记住我开启了,才可以用 * perms:拥有对某个资源的权限才能访问 * */ * */ Map<String,String> filter = new LinkedHashMap(); filter.put("/add","anon"); filter.put("/upd","authc"); bean.setFilterChainDefinitionMap(filter); bean.setLoginUrl("/tologin"); return bean; } // dafultwebSecurityManager @Bean(name="SecurityManager") public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("Userrealm") userrealm userrealm){ DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager(); // 关联Userrealm SecurityManager.setRealm(userrealm); return SecurityManager; } // 创建realm对象,需要自定义类 @Bean public userrealm Userrealm() { return new userrealm(); } }
realm对象需要引用外面的类 userrealm,我们需要继承AuthorizingRealm来获得授权,认证方法
package com.hyc.config; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class userrealm extends AuthorizingRealm { //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("授权=========>"); return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证=========>"); return null; } }
用户认证:
我们需要去编写config类,设置权限,什么路径需要什么权限,
@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); /* * 常用过滤器如下 * anon:无需认证访问 * authc:必须认证了才能访问 * user:记住我开启了,才可以用 * perms:拥有对某个资源的权限才能访问 role:该资源必须得到角色权限才可以访问 * */ Map<String,String> filter = new LinkedHashMap(); filter.put("/user/add","perms[user:add]"); filter.put("/user/upd","perms[user:upd]"); bean.setFilterChainDefinitionMap(filter); bean.setLoginUrl("/tologin"); bean.setUnauthorizedUrl("/unauth"); return bean; }
之后去realm去认证,认证的信息是我们从数据库user表中查询出来的数据
//认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证=========>"); // 获取当前的用户 Subject subject = SecurityUtils.getSubject(); // 封装用户数据 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; user user = userService.queryUserByName(token.getUsername()); if (user==null){ return null; } //认证的时候创建用户登陆的session Session session = subject.getSession(); //将用户的属性传入到session中 session.setAttribute("loginUser",user); //如何让我们的user可以全局使用,我们需要设置info中第一个参数为user return new SimpleAuthenticationInfo(user,user.getPassword() ,""); }
之后再认证之后获取用户对象授权,什么对象可以访问什么页面
//授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("授权=========>"); //授权信息 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //传递用户信息 Subject subject = SecurityUtils.getSubject(); user currentUser = (user) subject.getPrincipal(); //从数据库中获取授权角色 info.addStringPermission(currentUser.getParms()); info.addRole("user:add"); info.addRole("user:upd"); return info; }
登陆功能
controller
@RequestMapping("/login") public String login(String username,String password,Model model){ //获取角色对象 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username,password); try { subject.login(token);//判断令牌是否正确 return "index"; } catch (UnknownAccountException uae) {//用户名不存在 model.addAttribute("msg","用户名不存在"); return "login"; } catch (IncorrectCredentialsException ice) {//密码不存在 model.addAttribute("msg","密码错误"); return "login"; } }
这里有一个拓展可以做:就是密码加密处理
我们这里调用的login()方法会走上面我们配置的一系列流程