Shiro的实现机制(源码解析)

Shiro的实现机制(源码解析)

2023年7月26日发(作者:)

Shiro的实现机制(源码解析)⽂章⽬录什么是shiro?Apache Shiro官⽹上对Shiro的解释如下:Apache Shiro (pronounced “shee-roh”, the Japanese word for ‘castle’) is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management and can be used to secure any application - from the command line applications, mobile applications to the largest web and enterprise applications.----Shiro provides the application security API to perform the following aspects (I like to call these the 4 cornerstones of application security):* Authentication - proving user identity, often called user ‘login’.* Authorization - access control* Cryptography - protecting or hiding data from prying eyes* Session Management - per-user time-sensitive state⼤概意思就是说:shiro是⼀个功能强⼤且易于使⽤的Java安全框架,它的认证,授权,加密和会话管理可以⽤于保护任何应⽤程序——来⾃从命令⾏应⽤程序、移动应⽤程序到最⼤的web和企业应⽤程序。shiro为以下⼏个⽅⾯提供应⽤程序的安全API(应⽤程序安全的4⼤基⽯):Authentication - 提供⽤户⾝份认证,俗称登录Authorization - 访问权限控制Cryptography - 使⽤加密算法保护或者隐藏数据Session Management - ⽤户的会话管理Login这部分我会结合⾃⼰项⽬中的⼀些实例来做出分析:我的LoginController中对登录操作所做的处理:@RequestMapping("/doLogin") public String doLogin(String loginName, String password, HttpServletRequest request){ String result = "redirect:/index"; String fail = "/login"; String errorMsg = ""; // shiro认证 Subject subject = ject(); UsernamePasswordToken token = new UsernamePasswordToken(loginName, password); try { (token); User user = rByLogiName(loginName); if(user != null){ sword(""); //(user); Value().set(ame,user); } } catch (UnknownAccountException e) { result = fail; errorMsg = "账户不存在"; } catch (DisabledAccountException e) { result = fail; errorMsg = "账户存在问题"; } catch (AuthenticationException e) { result = fail; errorMsg = "密码错误"; } catch (Exception e) { result = fail; errorMsg = "登陆异常"; (sage(),e); } ribute("error",errorMsg); return result; }在进⾏登录操作的⽅法doLogin()⾥⾸先要做的是shiro认证这个过程。shiro认证这个过程主要分为三个部分:1:获取客户端输⼊放⼊⽤户名,密码。2:获取数据源中存放的数据即相应的⽤户名,密码。3:进⾏两者的⽐对,判断是否登录操作成功。先看⼀下shiro中是如何实现这个认证的过程的吧:这⾥的意思就是说,通过当前的⽤户对象Subject执⾏login()⽅法将⽤户信息传给Shiro的SecurityManager,⽽这个SecurityManager会将⽤户信息委托给内部登录模块,由内部登录模块来调⽤Realm中的⽅法来进⾏数据⽐对进⽽判断是否登录成功。那么我们就有疑问了,这⾥怎么有出现了三个关键词Subject,SecurityManager和Realm这⼜有什么⽤呢?Subject,SecurityManager和RealmSubject对于subject官⽹上是这样定义的:SubjectWhen you’re securing your application, probably the most relevant questions to ask yourself are, “Who is the current user?” or “Is the current user allowed

to do X”? It is common for us to ask ourselves these questions as we're writing code or designing user interfaces: applications are usually built based on user stories, and you want functionality represented (and secured) based on a per-user basis. So, the most natural way for us to think about security in our application is based on the current user. Shiro’s API fundamentally represents this way of thinking in its Subject word Subject is a security term that basically means "the currently executing user". It's just not called a 'User' because the word 'User' is usually associated with a human being. In the security world, the term 'Subject' can mean a human being, but also a 3rd party process, daemon account, or anything similar. It simply means 'the thing that is currently interacting with the software'. For most intents and purposes though, you can think of this as Shiro’s ‘User’ concept. You can easily acquire the Shiro Subject anywhere in your code as shown in Listing 1 below.⼤意就是:subject指的是当前⽤户,因为我们⼈的思维更倾向于某个⽤户有某个⾓⾊,因此可以理解为基于当前⽤户。(不过在安全领域,术语“Subject”可以指⼀个⼈,也可以指第三⽅进程、守护进程帐户或任何类似的东西。)它的获取⽅法在官⽅⽂档中定义为:相信在我上⾯的部分项⽬代码中⼤家也已经看到了,这块⽤法是固定的:import t;import tyUtils;...Subject currentUser = ject();⼀旦您获得了subject,就可以⽴即访问当前⽤户使⽤Shiro想要做的90%的事情,⽐如登录、注销、访问他们的会话、执⾏授权检查,等等.SecurityManagerSecurityManager是shiro架构核⼼,协调内部安全组件(如登录,授权,数据源等),⽤来管理所有的subject。这块会后续进⾏补充说明。Realm官⽹定义如下:RealmsThe third and final core concept in Shiro is that of a Realm. A Realm acts as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data.

That is, when it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an this sense a Realm is essentially a security-specific DAO: it encapsulates connection details for data sources and makes the associated data available to Shiro as needed. When configuring Shiro, you must specify at least one Realm to use for authentication and/or authorization. More than one Realm may be configured, but at least one is provides out-of-the-box Realms to connect to a number of security data sources (aka directories) such as LDAP, relational databases (JDBC), text configuration sources like INI and properties files, and more. You can plug-in your own Realm implementations to represent custom data sources if the default Realms do not meet your needs. Listing 4 below is an example of configuring Shiro (via INI) to use an LDAP directory as one of the application’s Realms.⼤意是指:Realm充当的是Shiro和应⽤程序安全数据之间的==“桥梁”或“连接器”==。也就是说,当实际需要与与安全相关的数据(如⽤户帐户)进⾏交互以执⾏⾝份验证(登录)和授权(访问控制)时,Shiro会从⼀个或多个为应⽤程序配置的Realm中查找这些内容。也是说Realm本质上是⼀个特定于安全性的DAO(逻辑处理):它封装了数据源的连接细节,并根据需要将关联的数据提供给Shiro。在配置Shiro时,必须指定⾄少⼀个⽤于⾝份验证和/或授权的领域。可以配置多个域,但⾄少需要⼀个。Shiro提供了开箱即⽤的领域,可以连接到许多安全数据源(即⽬录),如LDAP、关系数据库(JDBC)、⽂本配置源(如INI)和属性⽂件,等等。也可以⾃定义Realm实现来表⽰⾃定义数据源。(下⽂我会贴出项⽬中⾃定义的Realm做为参考)。下⾯是详细的shiro结构图:获取subject⾸先是第⼀步要获取客户端传来的数据 Subject subject = ject();login()(token);内部调⽤的是subject接⼝声明的⽅法:void login(AuthenticationToken token) throws AuthenticationException;我们来看⼀下login()的具体实现: public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal();

Subject subject = (this, token);

PrincipalCollection principals; String host = null; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject) subject; //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals: principals = pals; host = ; } else { principals = ncipals(); } if (principals == null || y()) { String msg = "Principals returned from ( token ) returned a null or " + "empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } pals = principals; ticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { = host; } Session session = sion(false); if (session != null) { n = decorate(session); } else { n = null; } }要注意的是内部调⽤的是(this, toke)⽅法。我们再来进⼀步的看⼀下(this, toke)的内部实现:⾸先SecurityManager中对login⽅法的声明:Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;实现类DefaultSecurityManager中对login()的实现public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = authenticate(token); } catch (AuthenticationException ae) { try { onFailedLogin(token, ae, subject); } catch (Exception e) { if (Enabled()) { ("onFailedLogin method threw an " + "exception. Logging and propagating original AuthenticationException.", e); } } throw ae; //propagate } Subject loggedIn = createSubject(token, info, subject); onSuccessfulLogin(token, info, loggedIn); return loggedIn; }在这⾥我们发现调⽤了authenticate(token)这个⽅法是从哪⾥来的呢?再来看看SecurityManager接⼝中的⽅法和它所继承的类:public interface SecurityManager extends Authenticator, Authorizer, SessionManager { Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException; void logout(Subject subject); Subject createSubject(SubjectContext context);}这⾥我们看到 SecurityManager 接⼝继承了 Authenticator 登录认证的接⼝⽐如登录(Authenticator),权限验证(Authorizer)等。再来看⼀看Authenticator接⼝中都声明了哪些⽅法:public interface Authenticator { public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException;}也就是我们刚才在DefaultSecurityManager中对login()的实现中调⽤的⽅法,忘了的⼩盆友可以回过头去看⼀眼哦,O(∩_∩)O哈哈~。AbstractAuthenticator中authenticate()的实现:public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { //

调⽤doAuthenticate⽅法 info = doAuthenticate(token); if (info == null) { ... } } catch (Throwable t) { ... } ...}调⽤了doAuthenticate(token)⽅法。我们再来看ModularRealmAuthenticator中doAuthenticate(token)⽅法的实现:protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); Collection realms = getRealms(); if (() == 1) { // Realm唯⼀时 return doSingleRealmAuthentication(or().next(), authenticationToken); } else { return doMultiRealmAuthentication(realms, authenticationToken); }}调⽤了doSingleRealmAuthentication(or().next(), authenticationToken)再往下看:doSingleRealmAuthentication的实现:protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) { if (!ts(token)) { ... } //

调⽤Realm的getAuthenticationInfo⽅法获取AuthenticationInfo信息 AuthenticationInfo info = henticationInfo(token); if (info == null) { ... } return info;}哇,我们看到了什么henticationInfo(token)它调⽤Realm的getAuthenticationInfo(token)⽅法。⽽在Realm中我们看⼀下⽤户认证⽅法重写:@Servicepublic class MyShiroRealm extends AuthorizingRealm { //⽤于⽤户查询 @Reference private UserService userService; //⽤户认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //

获取⽤户信息 String name = ncipal().toString(); User user = rByLogiName(name); if(user == null){ return null; }else{ SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, sword(), getName()); return simpleAuthenticationInfo; } } //⾓⾊权限和对应权限添加 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取登录⽤户名 String name= (String) maryPrincipal(); //查询⽤户名称 //User user = Name(name); //添加⾓⾊和权限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); /*for (Role role:es()) { //添加⾓⾊ e(eName()); for (Permission permission:missions()) { //添加权限 ingPermission(mission()); } }*/ return simpleAuthorizationInfo; }}主要重写了俩个⽅法:doGetAuthenticationInfo()主要是进⾏登录认证doGetAuthorizationInfo()主要是进⾏⾓⾊权限和对应权限的添加Shiro 配置要配置的是ShiroConfig类,Apache Shiro 核⼼通过 Filter 来实现(类似SpringMvc 通过DispachServlet 来主控制⼀样)filter主要是通过URL规则来进⾏过滤和权限校验,所以我们需要定义⼀系列关于URL的规则和访问权限。如下:@Configurationpublic class ShiroConfiguration { //将⾃⼰的验证⽅式加⼊容器 @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); dentialsMatcher(new CredentialsMatcher()); return myShiroRealm; } //权限管理,配置主要是Realm的管理认证 @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); lm(myShiroRealm()); return securityManager; } //Filter⼯⼚,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); urityManager(securityManager); Map map = new HashMap(); //登出 ("/logout","logout"); //对所有⽤户认证 ("/user/**","authc"); ("/org/**","authc"); ("/role/**","authc"); ("/menu/**","authc"); ("/log/**","authc"); ("/index","authc"); //其他资源不拦截 ("/**","anon"); ("/doLogin","anon"); //登录 inUrl("/login"); //⾸页 cessUrl("/index"); //错误页⾯,认证不通过跳转 uthorizedUrl("/error"); terChainDefinitionMap(map); return shiroFilterFactoryBean; }}⽅法⼀:myShiroRealm()⽅法主要是将我⾃定义的匹配器对象当做参数传给MyShiroRealm并返回。(也就是说把我⾃定义来判断规则告诉shiro让shiro来管理)⽽在我⾃定义的密码匹配器中是这样实现的:public class CredentialsMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { UsernamePasswordToken uToken =(UsernamePasswordToken) token; //获得⽤户输⼊的密码:(可以采⽤加盐(salt)的⽅式去检验) String inPassword = new String(sword()); //获得数据库中的密码 String dbPassword = (String) dentials(); n("inPassword:" + inPassword); n("dbPassword:" + dbPassword); //进⾏密码的⽐对 boolean flag = tePassword(inPassword,dbPassword); return flag; }}这⾥我使⽤的是将客户端的密码进⾏加盐处理之后再和我数据库中的数据进⾏⽐对判断。⽅法⼆:securityManager()实例化了DefaultWebSecurityManager类,将上⾯myShiroRealm()的返回值当做参数,也就是配置Realm的管理认证。⽅法三:shiroFilterFactoryBean(DefaultWebSecurityManager securityManager)Filter⼯⼚,设置对应的过滤条件和跳转条件。异常捕获在登录过程中可能会出现不同的异常,对于不同的异常,我们是如何处理的呢?当然不同的异常就要分类进⾏处理,⽐如密码错误和账户不存在就不能⼀概⽽论,对于这些问题,我们能做的就是将不同的异常进⾏捕获进⾏不同页⾯的跳转反馈给⽤户,提⾼⽤户体验,⽐如:try { (token); User user = rByLogiName(loginName); if(user != null){ sword(""); //(user); Value().set(ame,user); } } catch (UnknownAccountException e) { result = fail; errorMsg = "账户不存在"; } catch (DisabledAccountException e) { result = fail; errorMsg = "账户存在问题"; } catch (AuthenticationException e) { result = fail; errorMsg = "密码错误"; } catch (Exception e) { result = fail; errorMsg = "登陆异常"; (sage(),e); }这⾥要注意的是不要把⽗类写在前⾯捕获,否则所有的此类异常都会被⽗类捕获,⼦类就不会进⾏错误处理,⽆法得到错误的详细的归类处理。哈哈哈哈哈哈哈哈嗝,今天敲开熏,⼀直就很疑惑shiro的实现机制,今天终于明⽩啦。嗷嗷嗷开⼼开⼼。

发布者:admin,转转请注明出处:http://www.yc00.com/web/1690377757a340443.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信