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;

  1. Session start(SessionContext context);办法,基于指定的上下文初始化数据启动新会话。
  2. 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就认出是你这个用户了。