OIDC协议作为以OAuth2为基础衍生的出新的认证授权协议,将OAuth2的授权协议与OpenId的认证协议相结合,从而生产的新的sso协议OIDC协议(OpenID Connect)。本文讲解的是基于CAS 5.1.X 实现的OIDC搭建。
*本文章需要读者自行搭建CAS服务端
OIDC主要术语说明:http://openid.net/specs/openid-connect-basic-1_0.html#Terminology
- EU:End User:一个人类用户。
- RP:Relying Party,用来代指OAuth2中的受信任的客户端,身份认证和授权信息的消费方;
- OP:OpenID Provider,有能力提供EU认证的服务(比如OAuth2中的授权服务),用来为RP提供EU的身份认证信息
- IDToken:JWT格式的数据,包含EU身份认证的信息。
- UserInfo Endpoint:用户信息接口(受OAuth2保护),当RP使用AccessToken访问时,返回授权用户的信息,此接口必须使用HTTPS。
OIDC工作流程:
官网文档给出了详细的介绍,整个过程如下图(http://openid.net/specs/openid-connect-basic-1_0.html)
- RP发送一个认证请求给OP;
- OP对EU进行身份认证,然后提供授权;
- OP把ID Token和Access Token(需要的话)返回给RP;
- RP使用Access Token发送一个请求UserInfo EndPoint;
- UserInfo EndPoint返回EU的Claims。

CAS服务端集成OIDC
<dependency> <groupId>org.apereo.cas
groupId> <artifactId>cas-server-support-oidc
artifactId> <version>${cas.version}
version>
dependency>
三、配置文件
#--------------------openId connect------------------ #签名文件路径 cas.authn.oidc.jwksFile=classpath:/static/keystore.jwks #签发端地址 cas.authn.oidc.issuer=https://localhost:8888/cas/oidc/ #-------------------开启动态注册客户端------------------ cas.authn.oidc.dynamicClientRegistrationMode=OPEN #-------------------自定义字段------------------ cas.authn.oidc.userDefinedScopes.hbtvprofiles=id,name,mobile,email,avatar
{ "@class" : "org.apereo.cas.services.OidcRegisteredService", "clientId": "...", "clientSecret": "...", "serviceId" : "...", "name": "OIDC Test", "id": 10, "scopes" : [ "java.util.HashSet", [ "profile", "email", "address", "phone", "offline_access", "displayName", "eduPerson" ] ] }
OIDC 客户端实现
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0
modelVersion> <packaging>war
packaging> <groupId>com.hbtv.cas
groupId> <artifactId>portal
artifactId> <version>1.0
version> <properties>
<project.build.sourceEncoding>UTF-8
project.build.sourceEncoding>
<java.version>1.8
java.version>
<httpclient.version>4.5.2
httpclient.version>
properties>
<parent> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-parent
artifactId> <version>1.5.6.RELEASE
version>
parent> <dependencies>
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-tomcat
artifactId> <scope>provided
scope>
dependency>
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-web
artifactId>
dependency>
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-test
artifactId> <scope>test
scope>
dependency>
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-devtools
artifactId> <optional>true
optional>
dependency>
<dependency> <groupId>org.mybatis.spring.boot
groupId> <artifactId>mybatis-spring-boot-starter
artifactId> <version>1.1.1
version>
dependency>
<dependency> <groupId>mysql
groupId> <artifactId>mysql-connector-java
artifactId>
dependency>
<dependency> <groupId>com.github.pagehelper
groupId> <artifactId>pagehelper
artifactId> <version>4.1.6
version>
dependency>
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-thymeleaf
artifactId>
dependency>
<dependency> <groupId>com.auth0
groupId> <artifactId>java-jwt
artifactId> <version>3.3.0
version>
dependency> <dependency> <groupId>com.auth0
groupId> <artifactId>jwks-rsa
artifactId> <version>0.3.0
version>
dependency>
<dependency> <groupId>com.alibaba
groupId> <artifactId>fastjson
artifactId> <version>1.2.38
version>
dependency>
<dependency> <groupId>org.apache.commons
groupId> <artifactId>commons-lang3
artifactId> <version>3.4
version>
dependency>
<dependency> <groupId>org.apache.httpcomponents
groupId> <artifactId>httpclient
artifactId> <version>${httpclient.version}
version>
dependency>
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-data-redis
artifactId> <version>1.5.6.RELEASE
version>
dependency>
dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-maven-plugin
artifactId>
plugin>
plugins>
build>
project>
三、oidc客户端获取ID_Token
- 业务线接入授权第一步需要访问cas服务器,使用rest接口来获取id_token 和 access_token
- 如果没有登录,接口会将请求重定向到cas登陆页, 用户输入账号密码后,cas回调到业务线, 返回id_token 和
access_token - 如果用户已经登录到cas,接口会直接回调业务线,返回id_token 和 access_token
var params = {}, postBody = location.hash.substring(1), regex = /([^&=]+)=([^&]*)/g, m; while (m = regex.exec(postBody)) { params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); } // And send the token over to the server var req = new XMLHttpRequest(); // using POST so query isn't logged req.open('POST', 'http://' + window.location.host + '/cas_client/redirect/catchResponse', true); req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.onreadystatechange = function(e) {
if (req.readyState == 4) { if (req.status == 200) { var returnObj = eval('(' + req.responseText + ')'); if (returnObj.status === 200) { // If the response from the POST is 200 OK, perform a redirect window.location = 'http://' + window.location.host + '/cas_client/redirect/main' } else { alert(returnObj.message); } } // if the OAuth response is invalid, generate an error message else if (req.status == 400) { alert('There was an error processing the token') } else { alert('Something other than 200 was returned') } } }; req.send(postBody);
@RequestMapping(value = "/catchResponse", method = RequestMethod.POST) @ResponseBody public String catchResponse(HttpServletRequest request, HttpServletResponse response, String access_token, String token_type, String expires_in, String id_token) throws Exception { // 验证id_token的合法性 Map<String, Claim> claims = null; try { claims = JwtUtil.verifyToken(env, id_token); } catch (Exception e) { throw new CommonException(ResultCode.UNAUTHORIZED, "无效的idToken"); } // 验证accessToken的合法性步骤: // 1). // 对accesstoken进行16进制hash编码,编码方式和id_token头里面的编码方式需保持一致,例如头中alg是RS256,则使用SHA256进行Hash编码 // 2). // 取hash编码后的bytes数组的前半部分,进行base64编码 // 3). // base64后和id_token中的at_hash一样则验证通过 if (!StringUtils.equals(Base64Util.encode(HashUtil.HEXSHA256(access_token)), claims.get("at_hash").asString())) { throw new CommonException(ResultCode.UNAUTHORIZED, "AccessToken无效"); } // 返回access token return ResUtil.getJsonStr(ResultCode.OK, "验证成功", EncryUtils.encrypt(access_token + "," + claims.get("name").asString() + "," + new Date().getTime())); }
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/177535.html原文链接:https://javaforall.net
