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 -->View Code
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;View Code
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;View Code
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;View Code
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 @OverrideView Code
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 @OverrideView Code
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