@
1. SSOの概要
1.1シングルサインオンの定義
シングルサインオン(シングルサインオン)、英語名の略語SSO、SSOは、マルチシステム環境でシングルパーティシステムにログインすると、再度ログインしなくても関連する信頼できるシステムにアクセスできることを意味します。つまり、モノリシックシステムにログインする必要があるのは一度だけです。
1.2シングルサインオンロール
シングルサインオンには通常、次の3つの役割が含まれます。
①ユーザー(複数)。
②認証センター(1つ);
③Webアプリ(複数)。
PS:ここで言及するWebアプリケーションはSSOクライアントとして理解でき、認証センターはSSOサーバーと言えます。
1.3シングルサインオンの分類
httpプロトコルはステートレスプロトコルであるため、ログイン状態を維持するためにログイン情報を保存する必要があり、保存方法によってシングルサインオンの実装方法は2種類に分類できます。
- 1つは、より一般的なCookieに基づいており、たとえば、以下で説明するCASもCookieに基づいています。
- もう1つはセッションに基づいています。実際、これはセッション共有であると理解されています。シングルサインオンを実現できるのは、異なるサブシステム間でセッション共有を実装することだけです。詳細については、以前のブログを参照してください。これは、セッション共有を実現してシングルサインオンを実現することです。リンク
2. CASの概要
2.1 CASの簡単な定義
CAS(Center Authentication Service)は、エール大学が研究したオープンソースのシングルサインオンプロジェクトで、主にWebプロジェクトのシングルサインオン実装を提供し、Web SSOに属しています。
2.2 CASアーキテクチャ
CASアーキテクチャは、CASサーバーとCASクライアントに分かれています。
CASサーバーはCasオープンソースであり、githubにアクセスしてダウンロードして変更する必要があります。Casクライアント
はアプリまたはWebまたはPCにすることができ、CASは複数の開発言語、java、php、C#などをサポートしています。
PS:写真は公式ウェブサイトからのものです。ここでは簡単な紹介を示します。図からわかるように、CASは複数の認証方法をサポートしています。1つはLDAP、より一般的なデータベースデータベースJDBC、Active Directoryなどです。サポートされているプロトコルは次のとおりです。カスタムプロトコル、CAS、OAuth、OpenID、RESTful API、SAML1.1、SAML2.0など
2.3 CASの原則
以下はCASの公式
CASログインの画像で、他のシステムはCASサーバーとCASクライアントに分かれています。以下では、私の理解に基づいて少し説明します。
1.ユーザーはCASクライアントにアクセスしてリソースをリクエストします
2. CASサーバーにリダイレクトされたクライアントプログラム
3. CASサーバーは要求を認証し、TGC(チケット許可Cookie)があるかどうかを確認します。TGCがある場合は、すでにログインしていることを意味します。再度ログインする必要はありません。
4.認証に成功すると、サービスチケットが生成され、Cas Clientに返されます。クライアントはチケットをキャッシュします。チケットは通常、Cookieに配置されます。TGC(Ticket Granted Cookie)と呼びます。
5.次に、Casクライアントはチケットを取得してCasサーバーに再度アクセスし、CASサーバーはチケットの検証を実行します。
6. CASサーバーはチケットを検証し、合格後ユーザー情報を返します。ユーザーは情報を受け取った後にログインできます
このプロセスを見ると、おそらくCASがどのように実装されているのか理解できるでしょう。プロセスはたくさんあるようですが、これらのプロセスはバックグラウンドでCASによって実行されます。CASサービスとCASクライアント間の通信は、HttpUrlConnectionに基づいています
注意点:
- TGT(Ticket Granded Ticket)は、認証資格情報を格納するCookieです。TGTは、認証されたことを示します
- ST(サービスチケット)は、認証に使用される、CAS認定センターによって生成された偽造できないユニークなチケットです。
- ログインしていない場合、またはTGTが無効な場合は、アクセスしたときに認証センターにジャンプします。TGTがない場合は、認証に合格していないことを意味します。ログインページに直接リダイレクトされます。アカウントパスワードを入力すると、認証センターに再度リダイレクトされます。 ST、クライアントに戻ってTGCに保存
- ログインしていてTGTの有効期限が切れていない場合は、それを直接認証センターに持っていき、認証を受けてください。認証センターはTGTを見つけてクライアントにリダイレクトし、STを受け取り、クライアントはそのSTを認証センターに持っていき、確認を受けます。
3. CASサーバーの構築
3.1 CASはHTTPログイン設定をサポートします
デフォルトでは、CASはログインするためにhttpsリンクを必要としますが、学んだ場合は、最初にhttps制限を適用できます。このブログでは、以前に4.0から変更されたCas4.2.7を紹介します。詳細については、https://blog.csdn.net/u014427391を参照してください/記事/詳細/ 82083995
Cas4.2.7と4.0のモディフィケーションは異なります。Cas4.2.7バージョンはそれ自体でコンパイルする必要があります。MavenではなくGradleに基づいています。問題がある場合は、4.0をダウンロードできます。バージョン4.0はwarパッケージを提供しているため、自分でコンパイルする必要はありません。バージョン4.2.7の紹介、httpログインのサポート方法
cas4.2.7 cas-server-webapp / WEB-INF / cas.propertiesを変更する必要があり、すべて非セキュアに変更されました
tgc.secure=false
warn.cookie.secure=false
cas-server-webapp / resources / service / HTTPSandIMAPS-10000001.json
"serviceId" : "^(https|imaps)://.*"
プラスhttp
"serviceId" : "^(https|imaps|http)://.*"
cas-server-webapp / WEB-INF / view / jsp / default / ui / casLoginView.jspページにコメントして、それがHTTPSプロトコルのラベルブロックであるかどうかを確認します
<c:if test="${not pageContext.request.secure}">
<div id="msg" class="errors">
<h2><spring:message code="screen.nonsecure.title" /></h2>
<p><spring:message code="screen.nonsecure.message" /></p>
</div>
</c:if>
次にログインすると、セキュリティ以外のプロンプトは表示されません
3.2 CASサーバーの導入と運用
次に、Tomcat Webアプリケーションにwarパッケージをドロップすると、デプロイメントが開始され、デフォルトのアカウントパスワードcasuser / Mellon、cas4.2.7アカウントパスワードがcas.propertiesに書き込まれます。これは4.0とは異なります
accept.authn.users=casuser::Mellon
ログインは成功しました。もちろん、プロジェクトでこれを行うことはできません。これには、jdbcを設定したり、権限検証を追加したりする必要があります。
シングルサインアウト、リンクはhttp://127.0.0.1:8080/cas/logoutです。
4. CASクライアントアクセス
このブログでは、SpringBootに基づくCasクライアントアクセスを紹介し、データベースはmysqlを使用し、権限制御はShiroを使用します
Maven構成、およびShiroおよびCAS関連のjar:
CASとShiroの関連バージョン
<properties>
<shiro.version>1.2.3</shiro.version>
<shiro.spring.version>1.2.4</shiro.spring.version>
<shiro.encache.version>1.2.4</shiro.encache.version>
<cas.version>3.2.0</cas.version>
<shiro.cas.version>1.2.4</shiro.cas.version>
</properties>
プラスShiroおよびcas関連のjar
<!-- Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.spring.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.encache.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.cas.version}</version>
</dependency>
<!-- cas -->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>${cas.version}</version>
</dependency>
新しい環境クラスを作成し、casの構成リンクをいくつか保存します。
package org.muses.jeeplatform.core;
/**
* <pre>
* CAS配置环境类
* </pre>
*
* @author nicky.ma
* <pre>
* 修改记录
* 修改后版本: 修改人: 修改日期: 2019年05月25日 修改内容:
* </pre>
*/
public class CASConsts {
/* CAS单点登录配置 */
//Cas server地址
public static final String CAS_SERVER_URL_PREFIX = "http://localhost:8080/cas";
//Cas单点登录地址
public static final String CAS_LOGIN_URL = CAS_SERVER_URL_PREFIX +"/login";
//CAS单点登出地址
public static final String CAS_LOGOUT_URL = CAS_SERVER_URL_PREFIX + "/logout";
//对外提供的服务地址
public static final String SERVER_URL_PREFIX = "http://localhost:8081";
//Cas过滤器的urlPattern
public static final String CAS_FILTER_URL_PATTERN = "/jeeplatform";
//CAS客户端单点登录跳转地址
public static final String CAS_CLIENT_LOGIN_URL = CAS_LOGIN_URL + "?service="+SERVER_URL_PREFIX+CAS_FILTER_URL_PATTERN;
//CAS客户端单点登出
public static final String CAS_CLIENT_LOGOUT_URL = CAS_LOGOUT_URL + "?service="+SERVER_URL_PREFIX+CAS_FILTER_URL_PATTERN;
//登录成功地址
public static final String LOGIN_SUCCESS_URL = "/index";
//无权访问页面403
public static final String LOGIN_UNAUTHORIZED_URL = "/403";
}
ShiroCas構成クラス:
package org.muses.jeeplatform.config;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.muses.jeeplatform.core.shiro.ShiroRealm;
import org.muses.jeeplatform.web.filter.SysAccessControllerFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.muses.jeeplatform.core.CASConsts.*;
/**
* @author nicky.ma
*/
@Configuration
public class ShiroConfig {
private static final Logger LOG = LoggerFactory.getLogger(ShiroConfig.class);
/**
* 单点登出监听器
* @return
*/
@Bean
public ServletListenerRegistrationBean singleSignOutHttpSeessionListener(){
ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
bean.setListener(new SingleSignOutHttpSessionListener());
bean.setEnabled(true);
return bean;
}
/**
* 注册单点登出的过滤器
* @return
*/
@Bean
public FilterRegistrationBean singleSignOutFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setName("singleSignOutFilter");
bean.setFilter(new SingleSignOutFilter());
bean.addUrlPatterns("/*");
bean.setEnabled(true);
return bean;
}
@Bean
public FilterRegistrationBean authenticationFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new AuthenticationFilter());
bean.addUrlPatterns("/*");
bean.setName("CAS AuthenticationFilter");
bean.addInitParameter("casServerLoginUrl",CAS_SERVER_URL_PREFIX);
bean.addInitParameter("serverName",SERVER_URL_PREFIX);
return bean;
}
/**
* 单点登录校验
* @return
*/
@Bean
public FilterRegistrationBean validationFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("CAS Validation Filter");
registrationBean.addInitParameter("casServerUrlPrefix", CAS_SERVER_URL_PREFIX );
registrationBean.addInitParameter("serverName", SERVER_URL_PREFIX );
return registrationBean;
}
/**
* CAS过滤器
* @return
*/
@Bean
public CasFilter getCasFilter(){
CasFilter casFilter = new CasFilter();
casFilter.setName("casFilter");
casFilter.setEnabled(true);
casFilter.setFailureUrl(CAS_CLIENT_LOGIN_URL);
casFilter.setSuccessUrl(LOGIN_SUCCESS_URL);
return casFilter;
}
/**
* 定义ShrioRealm
* @return
*/
@Bean
public ShiroRealm myShiroRealm(){
ShiroRealm myShiroRealm = new ShiroRealm();
return myShiroRealm;
}
/**
* Shiro Security Manager
* @return
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//securityManager.setRealm(myShiroRealm());
securityManager.setSubjectFactory(new CasSubjectFactory());
return securityManager;
}
/**
* ShiroFilterFactoryBean
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager,CasFilter casFilter) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//注册Shrio Security Manager
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl(CAS_CLIENT_LOGIN_URL);
shiroFilterFactoryBean.setSuccessUrl(LOGIN_SUCCESS_URL);
shiroFilterFactoryBean.setUnauthorizedUrl(LOGIN_UNAUTHORIZED_URL);
//添加CasFilter到ShiroFilter
Map<String,Filter> filters = new HashMap<String,Filter>();
filters.put("casFilter",casFilter);
shiroFilterFactoryBean.setFilters(filters);
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
//Shiro集成CAS后需要添加该规则
filterChainDefinitionMap.put(CAS_FILTER_URL_PATTERN,"casFilter");
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/upload/**", "anon");
filterChainDefinitionMap.put("/plugins/**", "anon");
filterChainDefinitionMap.put("/code", "anon");
//filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/403", "anon");
//filterChainDefinitionMap.put("/logincheck", "anon");
filterChainDefinitionMap.put("/logout","anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
カスタムCasRealm:
package org.muses.jeeplatform.core.shiro;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.muses.jeeplatform.core.entity.admin.User;
import org.muses.jeeplatform.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import static org.muses.jeeplatform.core.CASConsts.CAS_FILTER_URL_PATTERN;
import static org.muses.jeeplatform.core.CASConsts.CAS_SERVER_URL_PREFIX;
/**
* @description 基于Shiro框架的权限安全认证和授权
* @author Nicky
* @date 2017年3月12日
*/
public class ShiroRealm extends CasRealm {
Logger LOG = LoggerFactory.getLogger(ShiroRealm.class);
/**注解引入业务类**/
@Resource
UserService userService;
@PostConstruct
public void initProperty(){
setCasServerUrlPrefix(CAS_SERVER_URL_PREFIX);
//客户端回调地址
setCasService(CAS_SERVER_URL_PREFIX + CAS_FILTER_URL_PATTERN);
}
/**
* 登录信息和用户验证信息验证(non-Javadoc)
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if(LOG.isInfoEnabled()) {
LOG.info("=>执行Shiro权限认证");
}
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
User user = userService.findByUsername(username);
/* 检测是否有此用户 */
if(user == null){
throw new UnknownAccountException();//没有找到账号异常
}
/* 检验账号是否被锁定 */
if(Boolean.TRUE.equals(user.getLocked())){
throw new LockedAccountException();//抛出账号锁定异常
}
/* AuthenticatingRealm使用CredentialsMatcher进行密码匹配*/
if(null != username && null != password){
return new SimpleAuthenticationInfo(username, password, getName());
}else{
return null;
}
}
/**
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用,负责在应用程序中决定用户的访问控制的方法(non-Javadoc)
* @see AuthorizingRealm#doGetAuthorizationInfo(PrincipalCollection)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
if(LOG.isInfoEnabled()) {
LOG.info("=>执行Shiro授权");
}
String username = (String)pc.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.getRoles(username));
authorizationInfo.setStringPermissions(userService.getPermissions(username));
return authorizationInfo;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
}
5、クライアント速度アクセス
上記の例は厄介です。クライアントにアクセスしたい場合は、サードパーティのスターターライブラリを使用して接続できます。
<!-- CAS依赖包 -->
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>1.5.0-GA</version>
</dependency>
yaml設定:
cas:
server-login-url: http://127.0.0.1:8080/cas/login
server-url-prefix: http://127.0.0.1:8080/cas
client-host-url: http://127.0.0.1:8081
validation-type: cas
# use-session: true
Springboot構成クラスを追加します。
package org.muses.jeeplatform.config;
import net.unicon.cas.client.configuration.CasClientConfigurerAdapter;
import net.unicon.cas.client.configuration.EnableCasClient;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCasClient
public class CASConfig extends CasClientConfigurerAdapter {
private static final String CAS_SERVER_URL_LOGIN = "http://localhost:8080/cas/login";
private static final String SERVER_NAME = "http://localhost:8081/";
// @Override
// public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
// super.configureAuthenticationFilter(authenticationFilter);
// //authenticationFilter.getInitParameters().put("authenticationRedirectStrategyClass","com.test.CustomAuthRedirectStrategy");
// }
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new AuthenticationFilter());
registrationBean.addUrlPatterns("/*");
Map<String, String> initParameters = new HashMap<String,String>(16);
initParameters.put("casServerLoginUrl",CAS_SERVER_URL_LOGIN);
initParameters.put("serverName",SERVER_NAME);
initParameters.put("ignorePattern","/logoutSuccess/*");
registrationBean.setOrder(1);
return registrationBean;
}
}