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
1
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);
}
1
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
1
image-20220713163247959

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
1
image-20220708153721513

3.登陆成功后会跳转以下地址,根据code获取access_token

http://127.0.0.1:8800/home/getAccessTokenByAuthorizationCode?code=OC-2-Q8gHLYqnZNq7elv4MCQEuUgudKP3u4Wt
1
image-20220708153805644

若请求成功,返回:

{
    "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
}
1
2
3
4
5
6
7

4.复制access_token,粘贴到输入框内 点击【获取用户信息】按钮,根据access_token获取用户信息

 http://127.0.0.1:8800/home/getUserInfo?accessToken=AT-2-JJgacR4ACtw1ivF74MEqWNodctxIGSea
1
image-20220708153921067

若请求成功,返回:

{
    "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"
}
1
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;
    }
1
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";

    }
1
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

image-20220713162640517

点击【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
1
image-20220713162730092

登陆成功后请求getAccessTokenAndUserInfo接口判断access_token是否存在,若存在跳转auth接口处理需要访问页面的逻辑,判断是否可以获取到用户信息若可以jumpUrl进行跳转相对应的页面。再次点击【http://127.0.0.1:8800/home2/getInfo 需要权限访问的接口【获取用户信息】】即可获取到用户信息

image-20220713162805682

# 3、demo程序

下载地址:client-oidc.zip

依赖jar包:platform-client-common-1.0.0.jar