integrated-oidc-java
# 1、基础参数说明
参数 | 说明 |
---|---|
casServerUrl | 基于cas登陆的登陆地址,包含contentPath,例如:http://127.0.0.1:8000/cas |
clientId | 中科软提供的appKey的值 |
clientSecret | 中科软提供的appSecret |
redirectUri | 当前应用服务的url地址,http://ip:port/contentPath,例如 http:127.0.0.1:8800 |
# 2、集成方式及说明
# 2.1、授权码模式(authorization-code)
- 拼接好链接,跳转到cas-server的授权验证接口(若无权限自动跳转到登陆页面)【方法名:CasOIDCApi.getAuthorizeCodeUrl】
- 若登陆成功,返回到redirectUri(会携带AuthorizationCode【参数名:code】)【方法名:CasOIDCApi.getAccessTokenByAuthorizationCode】
- 根据AuthorizationCode获取AccessToken【方法名:CasOIDCApi.getAccessTokenByAuthorizationCode】
- 根据AccessToken获取用户信息【方法名:CasOIDCApi.getUserInfo】
- 若AccessToken过期,可根据RefreshToken重新获取AccessToken【方法名:CasOIDCApi.getAccessTokenByRefreshToken】
关键代码如下:
application.properties
# 客户端唯一标识(CAS Server中json文件对应)
sino.oauth.clientId=abcd
# 客户端密钥(CAS Server中json文件对应)
sino.oauth.clientSecret=xyz
# CAS Server服务端URL
sino.oauth.casServerUrl=http://127.0.0.1:8000/cas
# 当前网站url http://ip:port/contentPath
sino.oauth.serverUrl=http://127.0.0.1:8800
2
3
4
5
6
7
8
HomeController
@Autowired
private SinoOIDCConfig sinoOIDCConfig;
/**
* index id_token模式
* @return
*/
@RequestMapping("index")
@ResponseBody
public String getIndex(){
return "getIndex。。。";
}
/**
* 获取code
* @return http://127.0.0.1:8000/cas/oidc/authorize?response_type=code&scope=openid%20profile&client_id=clientId&redirect_uri=URL
*/
@RequestMapping("/getAuthorizeCodeUrl")
public String getAuthorizeCodeUrl(){
return "redirect:" + CasOIDCApi.getAuthorizeCodeUrl(sinoOIDCConfig.getCasServerUrl(),
sinoOIDCConfig.getClientId(), sinoOIDCConfig.getServerUrl() + "/home/getAccessTokenByAuthorizationCode");
}
/**
* authorization_code 授权码模式获取accessToken
* grant_type=authorization_code :
* 使用最多的一种,根据code 获取accessToken。
* 常用于第三方登录,用户输入账号密码,同意并授权,第三方不知道用户的账号和密码,获取code之后,再根据code获取accessToken,使用accessToken获取用户同意获取的信息。
* @param code 授权成功后返回CODE
* @return
* {
* "access_token": "AT-1-SGfRwZIeLD4-XjV27Ax6Aswwo74yj-cx",
* "token_type": "bearer",
* "expires_in": 28800,
* "refresh_token": "RT-1-kZcSlf1IHcpPdSWuS1bBquAuf9o-jWA2",
* "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNhcyJ9.eyJqdGkiOiI1MzY5MDdkZi1mMGVjLTQ1MzgtYjIzNy1kZDYzMzRjZDI0NTAiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvY2FzL29pZGMvIiwiYXVkIjoiYWJjZCIsImV4cCI6MTY1NjQ0NDc1OCwiaWF0IjoxNjU2NDE1OTU4LCJuYmYiOjE2NTY0MTU2NTgsInN1YiI6IjEiLCJhbXIiOlsiUmVzdEF1dGhlbnRpY2F0aW9uSGFuZGxlciJdLCJzdGF0ZSI6IiIsIm5vbmNlIjoiIiwiYXRfaGFzaCI6IldnVjJVeTk1T1dGeEltZDBxU3FScFEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhYmNkIn0.P_pYAygNiCmo2OhSmASYBPySKS0-tsyED7NcZHO_jy19AinvfG_8A7VoPs0gvu8lILYnHHMNu2QzkSgTqHFTjnf67RryCtkqjUbvkzLJ_p9OnXO10tQdw45fA1HcWaRwtAcACcl1m-JI1a7u4uf-CfGkttPwQYvuYUqo3jshRLgtxitKd0K3SWPQR0oOQu1IA4pugZ99DsJr7ZrowjJd-7Q7mQqOIrQGJKgpMXSMr3DYccku25pcaVpqXSfDtIsOzux_1GxotlehWKf2qg1E_hqdOmzIeE1XSfJCfO51-YDdry9T6JaQSvQ-Jb_KqX-2FM2f_YjZg6D0jJO4j0n_zA"
* }
*/
@RequestMapping("/getAccessTokenByAuthorizationCode")
@ResponseBody
public JSONObject getAccessTokenByAuthorizationCode(@RequestParam(value = "code", required = true) String code) {
return CasOIDCApi.getAccessTokenByAuthorizationCode(sinoOIDCConfig.getCasServerUrl(),
sinoOIDCConfig.getClientId(), sinoOIDCConfig.getClientSecret(), code, sinoOIDCConfig.getServerUrl() + "/home/index");
}
/**
* 根据refresh_token获取access_token
* 上一次的令牌过期时,根据上一次请求返回的refresh_token来刷新accessToken.
* grant_type=refresh_token
* @param refreshToken refresh_token
* @return
* {
* "access_token": "AT-3-9JgxJ-HzZBJSmy8ALygfGxWX44ikp21t",
* "token_type": "bearer",
* "expires_in": 28800
* }
*/
@RequestMapping("/getAccessTokenByRefreshToken")
@ResponseBody
public JSONObject getAccessTokenByRefreshToken(@RequestParam(value = "refreshToken", required = true) String refreshToken) {
return CasOIDCApi.getAccessTokenByRefreshToken(sinoOIDCConfig.getCasServerUrl(),
sinoOIDCConfig.getClientId(), sinoOIDCConfig.getClientSecret(), refreshToken);
}
/**
* 根据accessToken获取用户信息
* @param accessToken access_token
* @return
* {
* "credentialType" : "UsernamePasswordCredential",
* "sinosoftRefreshToken" : "sinosoftRefreshTokend290f5b165f04a09a36aadd268fc74af",
* "sub" : "1",
* "sinosoftRefreshTokenExpireTime" : "2022-06-29 19:07:27",
* "sinosoftTokenExpireTime" : "2022-06-28 21:07:27",
* "systemCodes" : [ "systemcode1", "systemcode2", "systemcode3" ],
* "auth_time" : 1656415934,
* "id" : "1",
* "sinosoft" : "这里存储了根据不同主题返回的不同参数-sinosoft",
* "userinfo" : "{\"id\":\"1\",\"userType\":null,\"userName\":\"张三\",\"email\":\"zhangsan@qq.com\"}",
* "sinosoftToken" : "sinosoftToken707465600e9e4e99a871b173a8a79c4b"
* }
*/
@RequestMapping("/getUserInfo")
@ResponseBody
public JSONObject getUserInfo(@RequestParam(value = "accessToken", required = true) String accessToken) {
return CasOIDCApi.getUserInfo(sinoOIDCConfig.getCasServerUrl(), accessToken);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
效果展示
1.访问主页
http://127.0.0.1:8800/view/home3.jsp
2.点击【登陆】按钮,通过访问http://127.0.0.1:8800/home/getAuthorizeCodeUrl,跳转到cas-sever校验是否登陆。
http://127.0.0.1:8000/cas/login?service=http%3A%2F%2F127.0.0.1%3A8000%2Fcas%2Foauth2.0%2FcallbackAuthorize%3Fclient_name%3DCasOAuthClient%26client_id%3Dabcd%26redirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8800%252Fhome%252FgetAccessTokenByAuthorizationCode%26response_type%3Dcode
3.登陆成功后会跳转以下地址,根据code获取access_token
http://127.0.0.1:8800/home/getAccessTokenByAuthorizationCode?code=OC-2-Q8gHLYqnZNq7elv4MCQEuUgudKP3u4Wt
若请求成功,返回:
{
"access_token": "AT-5-3wdZBcpHJMs-aOGkugMliZgBk3eqfy5P",
"refresh_token": "RT-5-AQhpf4sXTfEJVIjyCfzwx1TYkQJHgEFh",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNhcyJ9.eyJqdGkiOiJmMjMwYjI2OS1mZmUxLTQwNjctYTUyYi03YThmODQ3NmYxY2QiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvY2FzL29pZGMvIiwiYXVkIjoiYWJjZCIsImV4cCI6MTY1NzMwODg0NCwiaWF0IjoxNjU3MjgwMDQ0LCJuYmYiOjE2NTcyNzk3NDQsInN1YiI6IjEiLCJhbXIiOlsiUmVzdEF1dGhlbnRpY2F0aW9uSGFuZGxlciJdLCJzdGF0ZSI6IiIsIm5vbmNlIjoiIiwiYXRfaGFzaCI6IlZfOWNtaVZUREdwYmRoeHIxVXRIZFEiLCJ1c2VyaW5mbyI6IntcImlkXCI6XCIxXCIsXCJ1c2VyVHlwZVwiOm51bGwsXCJ1c2VyTmFtZVwiOlwi5byg5LiJXCIsXCJlbWFpbFwiOlwiemhhbmdzYW5AcXEuY29tXCJ9IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWJjZCJ9.JpsTbRqutWNca2XtJEiurLA158xgFywojZlOHd3rYJKv69gKQmfCDwKqJW1Jr6BP3eFpbJd7adf04rYbsoVez8SkjwkwCcIodFwQhj-7M729VW_ZKFIGoCF5hfeVjJizYhhcxhjuflNl52AKFzD3rOEfrzi3_rrpjCFs4OgTaaNKimbUr50ygUc6OZngbxOixUktsZvr82c5RO0CDmlddItuEQDWjTkEwKsDlgEpk0aQZzE4sffDewKAO1_XIgjmuasrguSTf9-gN2Vabcr3UG4efC4Z4MY1Bjox-J0y3b8av487XgNyvG95EhjGDllkkFLQCMUe_pjUU0j7LG6UYw",
"token_type": "bearer",
"expires_in": 28800
}
2
3
4
5
6
7
4.复制access_token,粘贴到输入框内 点击【获取用户信息】按钮,根据access_token获取用户信息
http://127.0.0.1:8800/home/getUserInfo?accessToken=AT-2-JJgacR4ACtw1ivF74MEqWNodctxIGSea
若请求成功,返回:
{
"credentialType": "UsernamePasswordCredential",
"sinosoftRefreshToken": "sinosoftRefreshToken08251767687b43db9ec81f3870105db7",
"sub": "1",
"sinosoftRefreshTokenExpireTime": "2022-07-09 15:37:35",
"sinosoftTokenExpireTime": "2022-07-08 17:37:35",
"systemCodes": [
"systemcode1",
"systemcode2",
"systemcode3"
],
"auth_time": 1657265856,
"id": "1",
"sinosoft": "这里存储了根据不同主题返回的不同参数-sinosoft",
"userinfo": "{\"id\":\"1\",\"userType\":null,\"userName\":\"张三\",\"email\":\"zhangsan@qq.com\"}",
"sinosoftToken": "sinosoftTokened6606c1ef374f01b69387bd22314853"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2.2、前后端分离
基础说明: 假设 Home2Controller下的连接都需要权限才能访问。 理论上登出以后您不能还在当前页面了,所以理论上得跳走。 调用【需要权限访问的接口】时,会触发获取用户信息的过程。
关键代码
client-oidc/src/main/java/com/sinosoft/clientoidc/config/LoginFilter.java
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String servletPath = request.getServletPath();
AntPathMatcher antPathMatcher = new AntPathMatcher();
for (String path : authPathList) {
if (antPathMatcher.match(path, servletPath)) {
//只有用户信息,才继续
JSONObject joUserInfo = ConvertUtil.getUserJSONObject(request);
if (joUserInfo != null) {
filterChain.doFilter(request, response);
return;
}
//否则接口返回 100 用户未登录或登陆已过期
JSONObject joResultNoAuth = new JSONObject();
joResultNoAuth.put("code", 100);
joResultNoAuth.put("msg", "用户未登录或登陆已过期");
joResultNoAuth.put("jumpUrl", CasOAuth2Api.getAuthorizeCodeUrl(sinoOIDCConfig.getCasServerUrl(),
sinoOIDCConfig.getClientId(), sinoOIDCConfig.getServerUrl() + "/getAccessTokenAndUserInfo"));
doResponse(response, joResultNoAuth.toJSONString());
return;
}
}
filterChain.doFilter(request, response);
return;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
client-oidc/src/main/java/com/sinosoft/clientoidc/controller/IndexController.java
/**
* 这里处理登陆逻辑(前置:跳转到本页面前,需要处理前端,将跳转到本页面前的页面存储到cookie中,cookie的key为【jumpUrl】)
* 一般是用户未登录,需要登陆时跳转到本页面。
* 因为配置了yml,所以进入到controller的一定是有用户信息
* 先判断cookie中是否有需要跳转的页面。若没有,默认跳转到主页;若有,按照cookie中的跳转页面进行跳转
*
* @return
*/
@RequestMapping(value = {"/auth"})
public String auth(HttpServletRequest request, HttpServletResponse response) {
//默认跳转到主页
//String jumpUrl = "http://127.0.0.1:8100/home";
String jumpUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/index.jsp";
JSONObject joUserInfo = ConvertUtil.getUserJSONObject(request);
if (joUserInfo != null) {
//若存在用户信息,先判断cookie中是否有需要跳转的页面。若没有,默认跳转到主页;若有,按照cookie中的跳转页面进行跳转
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
cookie.getName();
if ("jumpUrl".equals(cookie.getName())) {
if (cookie.getValue() != null) {
jumpUrl = cookie.getValue();
//jquery.cookie 存储时会将值做encode处理
jumpUrl = ConvertUtil.urlDecode(jumpUrl);
}
//重置为无效
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
}
}
//如果没有默认的jumpUrl,跳转到默认位置,例如主页。
return "redirect:" + jumpUrl;
}
else {
//如果不存在用户信息,拼好参数,跳转到 /oauth2.0/authorize
return "redirect:" + CasOAuth2Api.getAuthorizeCodeUrl(sinoOIDCConfig.getCasServerUrl(),
sinoOIDCConfig.getClientId(), sinoOIDCConfig.getServerUrl() + "/getAccessTokenAndUserInfo");
}
}
/**
* 获取The access token.
* 根据AuthorizationCode获取AccessToken【方法名:CasOAuth2Api.getAccessTokenByAuthorizationCode】
* @return
*/
@RequestMapping("/getAccessTokenAndUserInfo")
public String getAccessTokenAndUserInfo(@RequestParam(value = "code", required = true) String code, HttpServletRequest request) {
JSONObject joAccessToken = CasOAuth2Api.getAccessTokenByAuthorizationCode(sinoOIDCConfig.getCasServerUrl(),
sinoOIDCConfig.getClientId(), sinoOIDCConfig.getClientSecret(), code, sinoOIDCConfig.getServerUrl() + "/home/index");
if (joAccessToken != null) {
String accessToken = ConvertUtil.toString(joAccessToken.get("access_token"));
String refreshToken = ConvertUtil.toString(joAccessToken.get("refresh_token"));
request.getSession().setAttribute("accessToken", accessToken);
request.getSession().setAttribute("refreshToken", refreshToken);
if (!StringUtils.isEmpty(accessToken)) {
JSONObject userInfo = CasOAuth2Api.getUserInfo(sinoOIDCConfig.getCasServerUrl(), accessToken);
//将用户信息存到session中
request.getSession().setAttribute("userInfo", userInfo);
return "redirect:/auth";
}
}
// 异常情况,无法获取到access_token 咱们默认跳转到默认页面。
log.error("/getAccessTokenAndUserInfo 异常情况,无法获取到access_token 咱们默认跳转到默认页面。");
return "redirect:/index.jsp";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
效果展示
访问主页 http://127.0.0.1:8800/view/home4.jsp
点击【http://127.0.0.1:8800/auth 确保已登陆【登陆】】,被LoginFilter拦截器进行拦截若未登陆则返回 code=100跳转登陆页
http://127.0.0.1:8000/cas/login?service=http%3A%2F%2F127.0.0.1%3A8000%2Fcas%2Foauth2.0%2FcallbackAuthorize%3Fclient_name%3DCasOAuthClient%26client_id%3Dabcd%26redirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8800%252FgetAccessTokenAndUserInfo%26response_type%3Dcode
登陆成功后请求getAccessTokenAndUserInfo接口判断access_token是否存在,若存在跳转auth接口处理需要访问页面的逻辑,判断是否可以获取到用户信息若可以jumpUrl进行跳转相对应的页面。再次点击【http://127.0.0.1:8800/home2/getInfo 需要权限访问的接口【获取用户信息】】即可获取到用户信息
# 3、demo程序
下载地址:client-oidc.zip
← http