简介
这里新开一篇文章,记录下shiro的相关知识。
Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。
首先,我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:
image.png
-
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
-
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
-
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
-
从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
身份验证
身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
-
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
-
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。接下来先进行一个基本的身份认证。 -
Realm Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
-
多Realm配置。 会按照realm声明的顺序进行使用
-
自定义的Realm一般继承AuthorizingRealm即可。
授权
Shiro支持三种方式的权限控制
-
编程式:通过写if/else授权代码块完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) { ... } -
注解式:通过在执行的Java方法上放置相应的注解完成: 没有权限将抛出相应的异常;
@RequiresRoles("admin")
public void hello() {
//有权限
} -
JSP/GSP标签:在JSP/GSP页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
角色/资源
Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles,不同的地方是它在判断为假的情况下会抛出UnauthorizedException异常
Shiro提供了isPermitted和isPermittedAll用于判断用户是否拥有某个权限或所有权限
与Web集成
Shiro提供了与Web集成的支持,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制,ShiroFilter类似于如Strut2/SpringMVC这种web框架的前端控制器,其是安全控制的入口点,其负责读取配置(如ini配置文件),然后判断URL是否需要登录/权限等工作。
- mvc的话就配置filter
- springboot的话就ShiroFilterFactoryBean
会话管理
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管JavaSE还是JavaEE环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web的透明支持、SSO单点登录的支持等特性。即直接使用Shiro的会话管理可以直接替换如Web容器的会话管理。
会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionsSecurityManager实现直接把会话管理委托给相应的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默认SecurityManager都继承了SessionsSecurityManager
Shiro提供了三个默认实现:
-
DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;
-
ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;并且可以设置session相关cookies的name等信息。
-
DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。
-
可以正常配置会话监听器
-
会话存储/持久化 Shiro提供SessionDAO用于会话的CRUD,即DAO(Data Access Object)模式实现:
-
会话验证
单点登录
Shiro 1.2开始提供了Jasig CAS单点登录的支持,单点登录主要用于多系统集成。
大体流程如下:
1、访问客户端需要登录的页面http://localhost:9080/ client/,此时会跳到单点登录服务器https://localhost:8443/ server/login?service=https://localhost:9443/ client/cas;
2、如果此时单点登录服务器也没有登录的话,会显示登录表单页面,输入用户名/密码进行登录;
3、登录成功后服务器端会回调客户端传入的地址:https://localhost:9443/client/cas?ticket=ST-1-eh2cIo92F9syvoMs5DOg-cas01.example.org,且带着一个ticket;
4、客户端会把ticket提交给服务器来验证ticket是否有效;如果有效服务器端将返回用户身份;
5、客户端可以再根据这个用户身份获取如当前系统用户/角色/权限信息。
接入流程:
- 导入shiro-cas 的依赖
- 自定义CasRealm
public class MyCasRealm extends CasRealm {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
}
- 配置
<bean id="casRealm" class="com.github.zhangkaitao.shiro.chapter13.realm.MyCasRealm">
<property name="userService" ref="userService"/>
……
<property name="casServerUrlPrefix" value="https://localhost:8443/chapter14-server"/>
<property name="casService" value="https://localhost:9443/chapter14-client/cas"/>
</bean>
casServerUrlPrefix:是CAS Server服务器端地址;
casService:是当前应用CAS服务URL,即用于接收并处理登录成功后的Ticket的;
- cas的token验证
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<property name="failureUrl" value="/casFailure.jsp"/>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="https://localhost:8443/chapter14-server/login?service=https://localhost:9443/chapter14-client/cas"/>
<property name="successUrl" value="/"/>
<property name="filters">
<util:map>
<entry key="cas" value-ref="casFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/casFailure.jsp = anon
/cas = cas
/logout = logout
/** = user
</value>
</property>
</bean>
loginUrl:https://localhost:8443/chapter15-server/login表示服务端端登录地址,登录成功后跳转到?service参数对于的地址进行客户端验证及登录;
“/cas=cas”:即/cas地址是服务器端回调地址,使用CasFilter获取Ticket进行登录。
OAuth2集成
OAuth角色
-
资源拥有者(resource owner):能授权访问受保护资源的一个实体,可以是一个人,那我们称之为最终用户;如新浪微博用户zhangsan;
-
资源服务器(resource server):存储受保护资源,客户端通过access token请求资源,资源服务器响应受保护资源给客户端;存储着用户zhangsan的微博等信息。
-
授权服务器(authorization server):成功验证资源拥有者并获取授权之后,授权服务器颁发授权令牌(Access Token)给客户端。
-
客户端(client):如新浪微博客户端weico、微格等第三方应用,也可以是它自己的官方应用;其本身不存储资源,而是资源拥有者授权通过后,使用它的授权(授权令牌)访问受保护资源,然后客户端把相应的数据展示出来/提交到服务器。“客户端”术语不代表任何特定实现(如应用运行在一台服务器、桌面、手机或其他设备)。
认证服务流程
-
1、首先通过如http://localhost:8080/chapter17-server/authorize
?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login访问授权页面; -
2、该控制器首先检查clientId是否正确;如果错误将返回相应的错误信息;
-
3、然后判断用户是否登录了,如果没有登录首先到登录页面登录;
-
4、登录成功后生成相应的auth code即授权码,然后重定向到客户端地址,如http://localhost:9080/chapter17-client/oauth2-login?code=52b1832f5dff68122f4f00ae995da0ed;在重定向到的地址中会带上code参数(授权码),接着客户端可以根据授权码去换取access token。
资源服务流程
- 1、首先通过如http://localhost:8080/chapter17-server/userInfo? access_token=828beda907066d058584f37bcfd597b6进行访问;
- 2、该控制器会验证access token的有效性;如果无效了将返回相应的错误,客户端再重新进行授权;
- 3、如果有效,则返回当前登录用户的用户名。
客户端
- OAuth2AuthenticationFilter。 该filter的作用类似于FormAuthenticationFilter用于oauth2客户端的身份验证控制;如果当前用户还没有身份验证,首先会判断url中是否有code(服务端返回的auth code),如果没有则重定向到服务端进行登录并授权,然后返回auth code;接着OAuth2AuthenticationFilter会用auth code创建OAuth2Token,然后提交给Subject.login进行登录;接着OAuth2Realm会根据OAuth2Token进行相应的登录逻辑。
- 1、首先判断有没有服务端返回的error参数,如果有则直接重定向到失败页面;
- 2、接着如果用户还没有身份验证,判断是否有auth code参数(即是不是服务端授权之后返回的),如果没有则重定向到服务端进行授权;
- 3、否则调用executeLogin进行登录,通过auth code创建OAuth2Token提交给Subject进行登录;
- 4、登录成功将回调onLoginSuccess方法重定向到成功页面;
- 5、登录失败则回调onLoginFailure重定向到失败页面。
OAuth2Realm
- 此Realm首先只支持OAuth2Token类型的Token;然后通过传入的auth code去换取access token;再根据access token去获取用户信息(用户名),然后根据此信息创建AuthenticationInfo;如果需要AuthorizationInfo信息,可以根据此处获取的用户名再根据自己的业务规则去获取。
总结
- 认证服务提供登录功能,返回authCode
- 客户端的Realm通过authCode去认证服务器获取accessToken
- 用accessToken访问资源服务器中的资源,获取用户信息(这步是不是可以换成从token中提取用户名,这样就不必要调用资源服务接口了)
- 拿到用户信息后客户端shiro做登录操作,本地处理角色等
JWT
下面介绍一下无状态的,基于jwt的认证处理方式。
比如使用了oauth2,得到了一个accessToken,就当作是jwt,里边包含了用户名。然后没有资源服务提供什么用户信息接口了。
有状态方式
- 有个jwt,每次请求验证这个jwt(可能有缓存,每次不需要真正执行查询等操作)
- 验证之后,存到session中一些东西,以后会用
无状态方式
- 有个jwt,每次请求验证jwt,从中获取用户名,角色等,这些都在jwt中本身包含(也可能有缓存和上边是一样的)
- 每次验证,可以刷新token有效期
- 可以使用redis等存储jwt用来验证
网友评论