shiro的入门应用
简略说下原理:
认证:调用登录接口后,在CustomRealm的doGetAuthenticationInfo()办法中,通过用户名查问到数据库中的明码,与登录接口中传入的明码比照,相等则认证胜利,返回Cookie。
受权:调用有权限注解的接口如/test,在CustomRealm的doGetAuthorizationInfo()办法中,通过用户名查问到数据库中的用户权限,与以后须要的权限比照,匹配则受权胜利,能够持续拜访。
Controller层
@RestControllerpublic class TestController { @RequestMapping(value = "/login",method = RequestMethod.GET) public String login(){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("liaowh","123"); subject.login(token); return "login"; } @RequestMapping(value = "/home",method = RequestMethod.GET) public String home(){ return "home"; } @RequiresRoles("user") @RequestMapping(value = "/test",method = RequestMethod.GET) public String test(){ return "test"; }
Shiro配置类
@Configurationpublic class ShiroConfig { @Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); return customRealm; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(customRealm()); return defaultWebSecurityManager; } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); chainDefinition.addPathDefinition("/login", "anon"); //除登录页面外的其余页面都须要认证 chainDefinition.addPathDefinition("/**", "authc"); return chainDefinition; }
自定义Realm
public class CustomRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(getClass()); //受权 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("doGetAuthorizationInfo"); //从主体传过来的认证信息中,获取用户名 String userName = (String)principalCollection.getPrimaryPrincipal(); //通过用户名从数据库中获取角色数据 Set<String> roles = getRolesByUserName(userName); //通过用户名从数据库中获取权限数据 Set<String> permissions = getPermissionsByUserName(userName); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setStringPermissions(permissions); simpleAuthorizationInfo.setRoles(roles); return simpleAuthorizationInfo; } //认证 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("doGetAuthenticationInfo"); //从主体传过来的认证信息中,获取用户名 String userName = (String) authenticationToken.getPrincipal(); //通过用户名从数据库中取得明码 String password = getPassWordByUserName(); if(password == null){ return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName,password,getName()); return authenticationInfo; } private String getPassWordByUserName() { return "123"; } private Set<String> getPermissionsByUserName(String userName) { Set<String> sets = new HashSet<String>(); sets.add("user:add"); return sets; } private Set<String> getRolesByUserName(String userName) { Set<String> sets = new HashSet<String>(); sets.add("user"); return sets; }}
Shiro源码解析之认证
认证(Authentication):
身份验证的过程,也就是证实一个用户的实在身份。为了证实用户身份,须要提供零碎了解和置信的身份信息和证据。须要通过向 Shiro 提供用户的身份(principals)和证实(credentials )来断定是否和零碎所要求的匹配。
Principals(身份)
是Subject的“标识属性”,能够是任何与Subject相干的标识,比如说名称(给定名称)、名字(姓或者昵称)、用户名、平安号码等等。
Primary Principals(次要身份)
尽管Shiro容许用户能够应用多个身份,然而还是心愿用户能有一个精准表明用户的身份,一个仅有的惟一标识 Subject值。在少数程序中常常会是一个用户名、邮件地址或者全局惟一的用户 ID。
Credentials(证实)
通常是只有Subject本人才晓得的秘密内容,用来证实Subject真正领有所需的身份。一些简略的证书例子如明码、指纹等。最常见的身份/证实是用户名和明码,用户名是所需的身份阐明,明码是证实身份的证据。如果一个提交的明码和零碎要求的统一,程序就认为该用户身份正确,因为其他人不应该晓得同样的明码。
调用login接口,subject.login(token)向Authentication提交身份和证实
@RequestMapping(value = "/login",method = RequestMethod.GET) public String login(){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("liaowh","123"); subject.login(token); return "login"; }
调用securityManager.login()
以后只配置了一个realm,执行doSingleRealmAuthentication()
调用咱们自定义realm的doGetAuthenticationInfo()办法
关键处:
来自subject提交的token和来自自定义realm的info的明码比照,此时前者明码来自申请,后者来自数据库
equals办法对token的明码和从数据库中取出来的info中的明码进行比照,如果认证雷同就返回true,否则将抛出异样
Shiro源码解析之受权
受权提供的API很多,上面解析下罕用的hasRole()和isPermitted()办法
hasRole():
isPermitted():
Shiro受权也能够用注解形式,Shiro权限注解基于编程式的SpringAop实现。
几个相干概念:
Advistor:相当于Aspect,由切入点Pointcut和告诉Advice组成。
Advice:告诉,定义了切面的工作是什么以及何时应用。
MethodInterceptor:盘绕加强。
上面大佬写的很好了,我就不班门弄斧了。
shiro注解受权源码实现
shiro源码解析之Filter初始化
ShiroFilterFactoryBean实现了FactoryBean接口,那么Spring在初始化的时候必然会调用ShiroFilterFactoryBean的getObject()获取实例。
进入createInstance()办法
这里次要有重要的两个步骤
1.通过createFilterChainManager()
办法创立一个FilterChainManager
进入createFilterChainManager()看看
第一步中的new DefaultFilterChainManager()初始化了一个LinkHashMap类型的filters,而后通过addFilter办法将Shiro默认的过滤器增加进该filters
在applyGlobalPropertiesIfNecessary()
办法中将某些默认filter中的loginUrl,SuccessUrl,UnauthorizedUrl替换成咱们设置的值。
下一步,Map<String, Filter> filters = getFilters(); 这里是获取咱们自定义的过滤器,默认是为空的,如果咱们配置了自定义的过滤器,那么会将其增加到filters中。至此filters中蕴含着Shiro内置的过滤器和咱们配置的所有过滤器。
再下一步,遍历filterChainDefinitionMap,这个filterChainDefinitionMap就是咱们在ShiroConfig中注入进去的拦挡规定配置。
chainName是咱们配置的过滤门路,chainDefinition是该门路对应的过滤器,这里阐明咱们能够为一个门路,同时配置多个过滤器。splitChainDefinition(chainDefinition)
办法会将chainDefinition中的过滤器分离出来与门路对应。
过滤门路和过滤器是一对多的关系,所以ensureChain()返回的NamedFilterList其实就是一个有着name为Key的List<Filter>,这个name保留的就是过滤门路,List保留着咱们配置的过滤器。获取到NamedFilterList后在将过滤器退出其中,这样过滤门路和过滤器映射关系就初始化好了。
2.将这个FilterChainManager注入PathMatchingFilterChainResolver中,它是一个过滤器执行链解析器。
咱们每次申请服务器都会调用这个办法,依据申请的URL去匹配过滤器执行链中的过滤门路,匹配上了就返回其对应的过滤器进行过滤。
Shiro源码解析之Session
首先说下HttpSession的原理,不然像我一样没用过session的一脸懵逼:
当客户端第一次拜访服务器的时候,此时客户端的申请中不携带任何标识给服务器,所以此时服务器无奈找到与之对应的session,所以会新建session对象,当服务器进行响应的时候,服务器会将session标识放到响应头的Set-Cookie中,会以key-value的模式返回给客户端.例:JSESSIONID=7F149950097E7B5B41B390436497CD21;其中JSESSIONID是固定的,而前面的value值对应的则是给该客户端新创建的session的ID,之后浏览器再次进行服务器拜访的时候,客户端会将此key-value放到cookie中一并申请服务器,服务器就会依据此ID寻找对应的session对象了;(当浏览器敞开后,会话完结,因为cookie隐没所以对应的session对象标识隐没,而对应的session仍然存在,但曾经成为报废数据期待GC回收了)。对应session的ID能够利用此办法失去:session.getId();
Shiro中的Session
Shiro提供了残缺的会话治理性能,不依赖底层容器,JavaSE利用和JavaEE利用都能够应用。SessionManager(会话管理器)治理着利用中所有Subject的会话,包含会话的创立、保护、删除、生效、验证等工作。
SessionManager 接口
public interface SessionManager { Session start(SessionContext context); Session getSession(SessionKey key) throws SessionException;}
SessionManager
接口是Shiro所有会话管理器的顶级接口。在此接口中申明了两个办法Session start(SessionContext context);
和Session getSession(SessionKey key) throws SessionException;
。
Session start(SessionContext context);
办法,基于指定的上下文初始化数据启动新会话。Session getSession(SessionKey key) throws SessionException;
依据指定的SessionKey
检索会话,如果找不到则返回null
。如果找到了会话,但会话但有效(已进行或已过期)则抛出SessionException
异样。
Shiro的web环境下的Session治理
ServletContainerSessionManager
是基于 spring web 实现的,只能用于 Web 环境下,它只是简略的封装了Serlvet
相干性能。DefaultWebSessionManager
是 shrio 反对用于 Web 环境下,只不过应用了本人的 session 治理。
ServletContainerSessionManager
ServletContainerSessionManager是Shiro默认的session管理器,
public class ServletContainerSessionManager implements WebSessionManager { // 实现start接口,创立Session实例 public Session start(SessionContext context) throws AuthorizationException { return createSession(context); } protected Session createSession(SessionContext sessionContext) throws AuthorizationException { // 这里sessionContext理论是DefaultWebSessionContext类型,从中提取HttpServletRequest HttpServletRequest request = WebUtils.getHttpRequest(sessionContext); // 从中提取HttpSession HttpSession httpSession = request.getSession(); // 构建HttpServletSession实例 String host = getHost(sessionContext); return createSession(httpSession, host); } protected Session createSession(HttpSession httpSession, String host) { return new HttpServletSession(httpSession, host); } // 获取 session public Session getSession(SessionKey key) throws SessionException { HttpServletRequest request = WebUtils.getHttpRequest(key); Session session = null; // 从HttpServletRequest获取session HttpSession httpSession = request.getSession(false); if (httpSession != null) { session = createSession(httpSession, request.getRemoteHost()); } return session; } }
ServletContainerSessionManager自身并不治理会话,它最终操作的还是HttpSession,所以只能在Servlet容器中起作用,它不能反对除应用HTTP协定的之外的任何会话。HttpServletSession持有servlet的HttpSession的援用,最终对HttpServletSession的操作都会委托给HttpSession(装璜模式)
ServletContainerSessionManager创立session的调用链如下时序图,能够看出Subject.login()登录胜利后用户的认证信息实际上是保留在HttpSession中的
DefaultWebSessionManager
DefaultWebSessionManager的Session创立与存储时序图:
Session的最终创立在SimpleSessionFactory的createSession
sessionId的创立在EnterpriseCacheSessionDAO的generateSessionId办法中,在assignSessionId办法外面存储在session中
后续等session创立完返回后,会将sessionId放入Cookie中
以上是shiro应用DefaultWebSessionManager,在登录的时候创立了session sessionId等,那么当下一个申请到服务器的时候,shiro是怎么辨认出以后是哪个用户的呢?
首先申请进入OncePerRequestFilter,而后进入到AbstractShiroFilter的doFilterInternal()办法如下:
createSubject()办法最终走到:
debug发现走到这里
进去瞧一瞧,如果以后申请是带了登录返回的Cookie,那么sessionId必然不为null,那么getSessionKey()返回的key也不为null,即进入getSession(key)办法
从以下两图能够看出,这个this.sessionManager.getSession(key)必定是在AbstractNativeSessionManager实现的,因为上面两个一个不再继承体系里,一个就是本人自身
来到这个这里,看到doxx的办法应该才是干活的
debug发现getSessionId()被DefaultWebSessionManager笼罩
看到这里应该终于懂了吧,就是取出申请中的cookie的id,回到这幅图,拿到了id有啥用啊,当然是要通过id拿用户的session,retrieveSessionFromDataSource,进去看看呗
最终从sessionDAO(默认是MemorySessionDAO)中获取session
而后呢,我都差点绕晕了你敢信,回到这两幅图
最终在save(subject)办法中,将subject保留到了session中。
总结下这个流程,登录之后返回带了jsessionId的Cookie给前端,前端下次再带上这个Cookie,Shiro从Cookie中拿到jsessionId,而后通过这个id能拿到session,那么shiro就认出是你这个用户了。