Shiro 是什么
Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能:
- 认证 - 用户身份识别,常被称为用户“登录”;
- 授权 - 访问控制;
- 密码加密 - 保护或隐藏数据防止被偷窥;
- 会话管理 - 每用户相关的时间敏感的状态。
对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro要简单的多。
Shiro的架构介绍
首先,来了解一下Shiro的三个核心组件:Subject, SecurityManager 和 Realms. 如下图:
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
Shiro完整架构图:
除前文所讲Subject、SecurityManager 、Realm三个核心组件外,Shiro主要组件还包括:
Authenticator :认证就是核实用户身份的过程。这个过程的常见例子是大家都熟悉的“用户/密码”组合。多数用户在登录软件系统时,通常提供自己的用户名(当事人)和支持他们的密码(证书)。如果存储在系统中的密码(或密码表示)与用户提供的匹配,他们就被认为通过认证。
Authorizer :授权实质上就是访问控制 - 控制用户能够访问应用中的哪些内容,比如资源、Web页面等等。
SessionManager :在安全框架领域,Apache Shiro提供了一些独特的东西:可在任何应用或架构层一致地使用Session API。即,Shiro为任何应用提供了一个会话编程范式 - 从小型后*立应用到大型集群Web应用。这意味着,那些希望使用会话的应用开发者,不必*使用Servlet或EJB容器了。或者,如果正在使用这些容器,开发者现在也可以选择使用在任何层统一一致的会话API,取代Servlet或EJB机制。
CacheManager :对Shiro的其他组件提供缓存支持。
Shiro的使用
首先搭建一个springBoot项目
http://www.cnblogs.com/nbfujx/p/7694768.html
Maven Plugin添加Shiro相关jar包
1 <!-- shiro --> 2 <dependency> 3 <groupId>org.apache.shiro</groupId> 4 <artifactId>shiro-core</artifactId> 5 <version>${shiro.version}</version> 6 </dependency> 7 <dependency> 8 <groupId>org.apache.shiro</groupId> 9 <artifactId>shiro-web</artifactId> 10 <version>${shiro.version}</version> 11 </dependency> 12 <dependency> 13 <groupId>org.apache.shiro</groupId> 14 <artifactId>shiro-spring</artifactId> 15 <version>${shiro.version}</version> 16 </dependency> 17 <dependency> 18 <groupId>org.apache.shiro</groupId> 19 <artifactId>shiro-ehcache</artifactId> 20 <version>${shiro.version}</version> 21 </dependency>
添加Shiro支持功能
1 package com.goku.webapi.config.Shiro; 2 3 import com.goku.webapi.mapper.ext.sysMenuExtMapper; 4 import com.goku.webapi.mapper.ext.sysUserExtMapper; 5 import com.goku.webapi.model.sysMenu; 6 import com.goku.webapi.model.sysRole; 7 import com.goku.webapi.model.sysUser; 8 import com.goku.webapi.service.sysUserService; 9 import org.apache.shiro.SecurityUtils; 10 import org.apache.shiro.authc.*; 11 import org.apache.shiro.authz.AuthorizationInfo; 12 import org.apache.shiro.authz.SimpleAuthorizationInfo; 13 import org.apache.shiro.realm.AuthorizingRealm; 14 import org.apache.shiro.session.Session; 15 import org.apache.shiro.subject.PrincipalCollection; 16 import org.springframework.beans.factory.annotation.Autowired; 17 18 /** 19 * Created by nbfujx on 2017/11/7. 20 */ 21 public class ShiroRealm extends AuthorizingRealm { 22 23 @Autowired 24 private sysUserExtMapper sysuserextmapper; 25 @Autowired 26 private sysMenuExtMapper sysmenuextmapper; 27 28 /** 29 *权限验证 30 * **/ 31 @Override 32 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 33 sysUser user = sysuserextmapper.selectByUsername((String) principalCollection.getPrimaryPrincipal()); 34 //把principals放session中 key=userId value=principals 35 SecurityUtils.getSubject().getSession().setAttribute(String.valueOf(user.getId()),SecurityUtils.getSubject().getPrincipals()); 36 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 37 //赋予角色 38 for(sysRole userRole:user.getSysrole()){ 39 info.addRole(userRole.getRoleName()); 40 } 41 //赋予权限 42 for(sysMenu menu:sysmenuextmapper.selectByUserId(user.getId())){ 43 info.addStringPermission(menu.getPerms()); 44 } 45 46 return info; 47 48 } 49 50 /** 51 * 登录验证 52 * **/ 53 @Override 54 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 55 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; 56 String userName=token.getUsername(); 57 sysUser user = sysuserextmapper.selectByUsername(token.getUsername()); 58 if (user != null) { 59 //设置用户session 60 Session session = SecurityUtils.getSubject().getSession(); 61 session.setAttribute("user", user); 62 return new SimpleAuthenticationInfo(userName,user.getPassword(),getName()); 63 } else { 64 return null; 65 } 66 } 67 }
添加Shiro配置文件
1 package com.goku.webapi.config.Shiro; 2 3 import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 4 import org.apache.shiro.cache.ehcache.EhCacheManager; 5 import org.apache.shiro.spring.LifecycleBeanPostProcessor; 6 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 7 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 8 import org.apache.shiro.web.filter.authc.LogoutFilter; 9 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 10 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 11 import org.springframework.beans.factory.annotation.Qualifier; 12 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 13 import org.springframework.context.annotation.Bean; 14 import org.apache.shiro.mgt.SecurityManager; 15 import org.springframework.context.annotation.Configuration; 16 import org.springframework.context.annotation.DependsOn; 17 18 import javax.servlet.Filter; 19 import java.util.LinkedHashMap; 20 import java.util.Map; 21 22 23 /** 24 * shiro配置类 25 * Created by nbfujx on 2017/11/7. 26 */ 27 @Configuration 28 public class ShiroConfig { 29 /** 30 * LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类, 31 * 负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。 32 * 主要是AuthorizingRealm类的子类,以及EhCacheManager类。 33 */ 34 @Bean(name = "lifecycleBeanPostProcessor") 35 public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 36 return new LifecycleBeanPostProcessor(); 37 } 38 39 40 /** 41 * ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm, 42 * 负责用户的认证和权限的处理,可以参考JdbcRealm的实现。 43 */ 44 @Bean(name = "shiroRealm") 45 @DependsOn("lifecycleBeanPostProcessor") 46 public ShiroRealm shiroRealm() { 47 ShiroRealm realm = new ShiroRealm(); 48 realm.setCacheManager(ehCacheManager()); 49 return realm; 50 } 51 52 /** 53 * EhCacheManager,缓存管理,用户登陆成功后,把用户信息和权限信息缓存起来, 54 * 然后每次用户请求时,放入用户的session中,如果不设置这个bean,每个请求都会查询一次数据库。 55 */ 56 @Bean(name = "ehCacheManager") 57 @DependsOn("lifecycleBeanPostProcessor") 58 public EhCacheManager ehCacheManager() { 59 return new EhCacheManager(); 60 } 61 62 /** 63 * SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。 64 */ 65 @Bean(name = "securityManager") 66 public DefaultWebSecurityManager securityManager() { 67 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 68 securityManager.setRealm(shiroRealm()); 69 securityManager.setCacheManager(ehCacheManager()); 70 return securityManager; 71 } 72 73 74 /** 75 * ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。 76 * 它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。 77 */ 78 @Bean(name = "shiroFilter") 79 public ShiroFilterFactoryBean shiroFilterFactoryBean() { 80 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 81 shiroFilterFactoryBean.setSecurityManager(securityManager()); 82 83 Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); 84 shiroFilterFactoryBean.setFilters(filters); 85 86 87 Map<String, String> filterChainDefinitionManager = new LinkedHashMap<String, String>(); 88 filterChainDefinitionManager.put("/login", "anon"); 89 filterChainDefinitionManager.put("/logout", "anon"); 90 filterChainDefinitionManager.put("/sysUser/*", "authc,perms");//"authc,perms[sysUser:*]"); 91 filterChainDefinitionManager.put("/sysMenu/*", "authc,perms");//"authc,perms[sysUser:*]"); 92 filterChainDefinitionManager.put("/*", "anon"); 93 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager); 94 95 shiroFilterFactoryBean.setLoginUrl("/notAuthc"); 96 shiroFilterFactoryBean.setSuccessUrl("/"); 97 shiroFilterFactoryBean.setUnauthorizedUrl("/notAuthz"); 98 return shiroFilterFactoryBean; 99 } 100 101 /** 102 * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。 103 */ 104 @Bean 105 @DependsOn("lifecycleBeanPostProcessor") 106 public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { 107 DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); 108 defaultAAP.setProxyTargetClass(true); 109 return defaultAAP; 110 } 111 112 /** 113 * AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类, 114 * 内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。 115 */ 116 @Bean 117 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { 118 AuthorizationAttributeSourceAdvisor aASA = new AuthorizationAttributeSourceAdvisor(); 119 aASA.setSecurityManager(securityManager()); 120 return aASA; 121 } 122 123 124 125 126 }
添加login验证类
1 package com.goku.webapi.controller.impl; 2 3 import com.alibaba.fastjson.JSON; 4 import com.goku.webapi.controller.loginController; 5 import com.goku.webapi.util.enums.returnCode; 6 import com.goku.webapi.util.message.returnMsg; 7 import org.apache.shiro.SecurityUtils; 8 import org.apache.shiro.authc.AuthenticationException; 9 import org.apache.shiro.authc.UsernamePasswordToken; 10 import org.apache.shiro.crypto.hash.Md5Hash; 11 import org.apache.shiro.session.SessionException; 12 import org.apache.shiro.subject.Subject; 13 import org.springframework.beans.factory.annotation.Autowired; 14 import org.springframework.boot.autoconfigure.web.ErrorAttributes; 15 import org.springframework.boot.autoconfigure.web.ErrorController; 16 import org.springframework.http.HttpStatus; 17 import org.springframework.web.bind.annotation.RequestMapping; 18 import org.springframework.web.bind.annotation.RequestMethod; 19 import org.springframework.web.bind.annotation.RequestParam; 20 import org.springframework.web.bind.annotation.RestController; 21 import org.springframework.web.context.request.RequestAttributes; 22 import org.springframework.web.context.request.ServletRequestAttributes; 23 24 import javax.servlet.http.HttpServletRequest; 25 import java.util.HashMap; 26 import java.util.Map; 27 28 29 /** 30 * Created by nbfujx on 2017-11-07. 31 */ 32 @RestController 33 public class loginControllerImpl implements loginController,ErrorController { 34 35 36 private final static String ERROR_PATH = "/error"; 37 38 @Autowired 39 private ErrorAttributes errorAttributes; 40 41 @RequestMapping(value = "/login", method = RequestMethod.GET)//测试方法 实际用post方法 42 public String login( 43 @RequestParam(value = "username", required = true) String userName, 44 @RequestParam(value = "password", required = true) String password, 45 @RequestParam(value = "rememberMe", required = true, defaultValue = "false") boolean rememberMe 46 ) { 47 String passwordmd5 = new Md5Hash(password, "2").toString(); 48 Subject subject = SecurityUtils.getSubject(); 49 UsernamePasswordToken token = new UsernamePasswordToken(userName, passwordmd5); 50 token.setRememberMe(rememberMe); 51 try { 52 subject.login(token); 53 } catch (AuthenticationException e) { 54 e.printStackTrace(); 55 return JSON.toJSONString (new returnMsg(returnCode.ERROR)); 56 } 57 return JSON.toJSONString (new returnMsg(returnCode.SUCCESS)); 58 } 59 60 61 @Override 62 @RequestMapping(value = "/logout", method = RequestMethod.GET) 63 public String logout() { 64 Subject subject = SecurityUtils.getSubject(); 65 try { 66 subject.logout(); 67 }catch (SessionException e){ 68 e.printStackTrace(); 69 return JSON.toJSONString (new returnMsg(returnCode.ERROR)); 70 } 71 return JSON.toJSONString (new returnMsg(returnCode.SUCCESS)); 72 } 73 74 @Override 75 @RequestMapping(value = "/notAuthc", method = RequestMethod.GET) 76 public String notAuthc() { 77 return JSON.toJSONString (new returnMsg(returnCode.NOTAUTHC)); 78 } 79 80 @Override 81 @RequestMapping(value = "/notAuthz", method = RequestMethod.GET) 82 public String notAuthz() { 83 return JSON.toJSONString (new returnMsg(returnCode.NOTAUTHZ)); 84 } 85 86 @Override 87 @RequestMapping(value =ERROR_PATH) 88 public String error(HttpServletRequest request) 89 { 90 Map<String, Object> body = getErrorAttributes(request, getTraceParameter(request)); 91 return JSON.toJSONString (new returnMsg(returnCode.ERROR,body)); 92 } 93 94 @Override 95 public String getErrorPath() { 96 return ERROR_PATH; 97 } 98 99 private boolean getTraceParameter(HttpServletRequest request) { 100 String parameter = request.getParameter("trace"); 101 if (parameter == null) { 102 return false; 103 } 104 return !"false".equals(parameter.toLowerCase()); 105 } 106 107 private Map<String, Object> getErrorAttributes(HttpServletRequest request,boolean includeStackTrace) { 108 RequestAttributes requestAttributes = new ServletRequestAttributes(request); 109 Map<String, Object> map = this.errorAttributes.getErrorAttributes(requestAttributes,includeStackTrace); 110 String URL = request.getRequestURL().toString(); 111 map.put("URL", URL); 112 return map; 113 } 114 115 }
添加业务类,业务方法增加RequiresPermissions权限注解
有权限
1 @Override 2 @RequestMapping(value="getUser/{id}", method = RequestMethod.GET) 3 @RequiresPermissions(value={"sysUser:selectByid"}) 4 public String selectByid(@PathVariable String id) { 5 this.logger.info("selectByid"); 6 return JSON.toJSONString (new returnMsg(returnCode.SUCCESS,sysuserService.selectByid(id))); 7 }
无权限
1 @Override 2 @RequestMapping(value="getMenu/{id}", method = RequestMethod.GET) 3 @RequiresPermissions(value={"sysMenu:selectByid"}) 4 public String selectByid(@PathVariable long id) { 5 return JSON.toJSONString (new returnMsg(returnCode.SUCCESS,sysmenuService.selectByid(id))); 6 }
Shiro的使用验证
首先我们访问未登录时的资源
进行登录
我们访问登录后有权限的资源
我们访问登录后没有权限的资源
我们可以对每个方法增加权限控制
GITHUB
github : https://github.com/nbfujx/learn-java-demo/tree/master/Goku.WebService.Simple.Shiro