# 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:8700

# 2、集成方式及说明

# 1、授权码模式(authorization-code)

  • 拼接好链接,跳转到cas-server的授权验证接口(若无权限自动跳转到登陆页面)【方法名:CasOAuth2Api.getAuthorizeCodeUrl】
  • 若登陆成功,返回到redirectUri(会携带AuthorizationCode【参数名:code】)【方法名:CasOAuth2Api.getAccessTokenByAuthorizationCode】
  • 根据AuthorizationCode获取AccessToken【方法名:CasOAuth2Api.getAccessTokenByAuthorizationCode】
  • 根据AccessToken获取用户信息【方法名:CasOAuth2Api.getUserInfo】
  • 若AccessToken过期,可根据RefreshToken重新获取AccessToken【方法名:CasOAuth2Api.getAccessTokenByRefreshToken】

关键代码如下

client-oauth2/src/main/resources/application.properties

# 客户端唯一标识(CAS Server中json文件对应)
sino.oauth.clientId=oauth2-clientId-20220725
# 客户端密钥(CAS Server中json文件对应)
sino.oauth.clientSecret=oauth2-clientId-20220725
# 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:8700
1
2
3
4
5
6
7
8

client-oauth2/src/main/java/com/sinosoft/clientoauth2/controller/HomeController.java

		@Autowired
    private SinoOauthConfig sinoOauthConfig;

    /**
     * http://127.0.0.1:8700/home/getAuthorizeCodeUrl
     * 重定向到 CAS Server进行认证从而获取code
     * 拼接好链接,跳转到cas-server的授权验证接口(若无权限自动跳转到登陆页面)【方法名:CasOAuth2Api.getAuthorizeCodeUrl】
     * 若登陆成功,返回到redirectUri(会携带AuthorizationCode【参数名:code】)【参考controller名:getAccessTokenByAuthorizationCode】
     * @return
     */
    @RequestMapping("/getAuthorizeCodeUrl")
    public String getAuthorizeCodeUrl(){
        return "redirect:" + CasOAuth2Api.getAuthorizeCodeUrl(sinoOauthConfig.getCasServerUrl(), sinoOauthConfig.getClientId(), sinoOauthConfig.getServerUrl() + "/home/getAccessTokenByAuthorizationCode");
    }

    /**
     * http://127.0.0.1:8700/home/getAccessTokenByAuthorizationCode?code=xxxx
     * 获取The access token.
     * 根据AuthorizationCode获取AccessToken【方法名:CasOAuth2Api.getAccessTokenByAuthorizationCode】
     *  @return
     */
    @RequestMapping("/getAccessTokenByAuthorizationCode")
    @ResponseBody
    public JSONObject getAccessTokenByAuthorizationCode(@RequestParam(value = "code", required = true) String code, HttpServletRequest request) {
        //1、响应对象
        JSONObject joResult = new JSONObject();

        //2、校验
        if (StringUtils.containsWhitespace(code)){
            joResult.put("code", 500);
            joResult.put("msg", "code参数不能为空");
            return joResult;
        }

        //3、获取 ticket,并根据ticket存储session信息。
        SessionUtil.recordSession(request);
        SinoHttpResult sinoHttpAccessToken = CasOAuth2Api.getAccessTokenByAuthorizationCode(sinoOauthConfig.getCasServerUrl(), sinoOauthConfig.getClientId(), sinoOauthConfig.getClientSecret(), code, sinoOauthConfig.getServerUrl() + "/home/index");
        if (sinoHttpAccessToken.getCode() == 200) {
            joResult = sinoHttpAccessToken.getResult();
            String accessToken = ConvertUtil.toString(joResult.get("access_token"));
            request.getSession().setAttribute("accessToken", accessToken);
        }
        return joResult;
    }

    /**
     * http://127.0.0.1:8700/home/getAccessTokenByAuthorizationCode?refreshToken=xxxx
     * 根据 refreshToken 获取 accessToken
     * 若AccessToken过期,可根据RefreshToken重新获取AccessToken【方法名:CasOAuth2Api.getAccessTokenByRefreshToken】
     * @param refreshToken
     * @return
     */
    @RequestMapping("/getAccessTokenByRefreshToken")
    @ResponseBody
    public JSONObject getAccessTokenByRefreshToken(@RequestParam(value = "refreshToken", required = true) String refreshToken) {
        //1、响应对象
        JSONObject joResult = new JSONObject();

        //2、校验
        if (StringUtils.containsWhitespace(refreshToken)){
            joResult.put("code", 500);
            joResult.put("msg", "refreshToken参数不能为空");
            return joResult;
        }

        //3、发起请求,根据 refreshToken 获取 accessToken
        SinoHttpResult accessTokenByRefreshToken = CasOAuth2Api.getAccessTokenByRefreshToken(sinoOauthConfig.getCasServerUrl(), sinoOauthConfig.getClientId(), sinoOauthConfig.getClientSecret(), refreshToken);
        if (accessTokenByRefreshToken.getCode() == 200) {
            joResult = accessTokenByRefreshToken.getResult();
        }

        return joResult;
    }

    /**
     * http://127.0.0.1:8700/home/getUserInfo?accessToken=xxxxxxxxxxxxxxxxxxxx&refreshToken=cccccccccccccccccc
     * 通过 access_token 参数 获取用户信息
     * 根据AccessToken获取用户信息【方法名:CasOAuth2Api.getUserInfo】
     * @return
     */
    @RequestMapping("/getUserInfo")
    @ResponseBody
    public JSONObject getUserInfo(@RequestParam(value = "accessToken", required = true) String accessToken, @RequestParam(value = "refreshToken", required = false) String refreshToken) {
        //1、响应对象
        JSONObject joResult = new JSONObject();

        //2、校验
        if (StringUtils.containsWhitespace(accessToken)){
            joResult.put("code", 500);
            joResult.put("msg", "accessToken参数不能为空");
            return joResult;
        }

        //3、通过 access_token 参数 获取用户信息
        SinoHttpResult userInfo = CasOAuth2Api.getUserInfo(sinoOauthConfig.getCasServerUrl(), accessToken);
        if (userInfo == null || userInfo.getCode() == null) {
            System.out.println("===========  获取用户信息异常。 =============");
            joResult.put("code", 500);
            joResult.put("msg", "获取用户信息异常");
            return joResult;
        }

        //4、返回信息
        //4.1若access_token未过期,获取用户信息。
        //4.2若access_token已过期,根据refreshToken重新获取access_token。
        if (200 == userInfo.getCode()) {
            //说明获取用户信息成功
            return userInfo.getResult();
        }
        else if (401 == userInfo.getCode()) {
            if (StringUtils.containsWhitespace(refreshToken)){
                joResult.put("code", 500);
                joResult.put("msg", "refreshToken参数不能为空");
                return joResult;
            }

            //说明token已过期,  根据refreshToken重新获取access_token
            SinoHttpResult result_new_access_token = CasOAuth2Api.getAccessTokenByRefreshToken(sinoOauthConfig.getCasServerUrl(), sinoOauthConfig.getClientId(), sinoOauthConfig.getClientSecret(), refreshToken);
            if (result_new_access_token.getCode() == 200) {
                JSONObject jo_new_access_token = result_new_access_token.getResult();

                String new_access_token = jo_new_access_token.get("access_token") == null ? "" : jo_new_access_token.get("access_token").toString();
                System.out.println("以前的token过期,获取到新的 new_access_token:" + new_access_token);

                userInfo = CasOAuth2Api.getUserInfo(sinoOauthConfig.getCasServerUrl(), new_access_token);
                if (userInfo == null || userInfo.getCode() == null) {
                    System.out.println("===========  获取用户信息异常。 =============");
                    joResult.put("code", 500);
                    joResult.put("msg", "获取用户信息异常");
                    return joResult;
                }

                //说明获取用户信息成功
                return userInfo.getResult();
            }
            else {
                System.out.println("===========  根据refreshToken重新获取access_token 失败。 =============");
                return null;
            }
        }
        else {
            System.out.println("===========  获取用户信息失败,特殊情况: =============");
            System.out.println("code: " + userInfo.getCode());
            System.out.println("msg: " + userInfo.getMsg());

            joResult.put("code", 500);
            joResult.put("msg", "获取用户信息失败");
            return joResult;
        }
    }


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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

具体前端代码参考 client-oauth2/src/main/webapp/view/home.jsp

效果展示

首页访问http://127.0.0.1:8700/index.jsp

image-20220712145218737

点击 常规请求 进入主页http://127.0.0.1:8700/view/home.jsp

image-20220712111012006

点击 http://127.0.0.1:8700/home/getAuthorizeCodeUrl 重定向到 CAS Server进行认证从而获取code

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%3D20180901%26redirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8700%252Fhome%252FgetAccessTokenByAuthorizationCode%26response_type%3Dcode
1
image-20220712145326341

登陆成功后则 跳转

http://127.0.0.1:8700/home/getAccessTokenByAuthorizationCode?code=OC-1-BUR0H6trxQ9Goy2Qi1OshFnOxegba5Jd
1
image-20220712141533621

复制access_token用于获取用户信息

image-20220712141650374

# 2、前后端分离

关键代码如下

具体前端代码参考 client-oauth2/src/main/webapp/view/home2.jsp

client-oauth2/ src/main/java/com/sinosoft/clientoauth2/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(sinoOauthConfig.getCasServerUrl(), sinoOauthConfig.getClientId(), sinoOauthConfig.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

client-oauth2/src/main/java/com/sinosoft/clientoauth2/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(sinoOauthConfig.getCasServerUrl(), sinoOauthConfig.getClientId(), sinoOauthConfig.getServerUrl() + "/getAccessTokenAndUserInfo");
        }

    }
    /**
     * 获取The access token.
     * 根据AuthorizationCode获取AccessToken【方法名:CasOAuth2Api.getAccessTokenByAuthorizationCode】
     *  @return
     */
    @RequestMapping("/getAccessTokenAndUserInfo")
    public String getAccessTokenAndUserInfo(@RequestParam(value = "code", required = true) String code, HttpServletRequest request) {
        SinoHttpResult resAccessToken = CasOAuth2Api.getAccessTokenByAuthorizationCode(sinoOauthConfig.getCasServerUrl(), sinoOauthConfig.getClientId(), sinoOauthConfig.getClientSecret(), code, sinoOauthConfig.getServerUrl() + "/home/index");
        if (resAccessToken != null) {
            JSONObject joAccessToken = resAccessToken.getResult();
            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)) {
                SinoHttpResult userInfo = CasOAuth2Api.getUserInfo(sinoOauthConfig.getCasServerUrl(), accessToken);
                //将用户信息存到session中
                request.getSession().setAttribute("userInfo", userInfo.getResult());
                
                //获取到用户信息后,记录当前用户session信息,方便后续被动退出逻辑处理
                SessionUtil.recordSession(request);

                return "redirect:/auth";
                // JSONObject result = new JSONObject();
                // result.put("code", 0);
                // result.put("msg", "获取用户成功");
                // result.put("data", userInfo.toJSONString());
                // return result.toJSONString();
            }
        }
        // JSONObject errResult = new JSONObject();
        // errResult.put("code", 500);
        // errResult.put("msg", "joAccessToken为空");
        // return errResult.toJSONString();
        // 异常情况,无法获取到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
73
74
75
76
77
78
79
80
81
82
83
84

效果展示

访问主页 http://127.0.0.1:8700/view/home2.jsp

image-20220713160421001

点击【http://127.0.0.1:8700/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%3D20180901%26redirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8700%252FgetAccessTokenAndUserInfo%26response_type%3Dcode
1
image-20220711174139852

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

image-20220712144615506

# 3、oauth2的退出

退出分成2中类型:主动退出、被动退出。

主动退出就是在平台上点击退出按钮,整个的退出cas。

但是,因为登陆时缓存了用户信息,我们还需要有一个接口,用于让平台调用,将我们缓存的用户清理掉。

代码如下:

	/**
     * 【主动】退出方法
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = {"/logout"})
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("logout");

        //1 session过期
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 过期会话
            session.invalidate();
        }

        //2 清除本地cookie
        // 获取Cookies数组
        Cookie[] cookies = request.getCookies();
        // 迭代查找并清除Cookie
        for (Cookie cookie : cookies) {
            //将cookie.setMaxAge(0)表示删除cookie.
            cookie.setMaxAge(0);
            cookie.setPath("/");
            response.addCookie(cookie);
        }

        //3 保证cas登出
        //3.1 可以直接登出,跳转到  "redirect:http://cas.server.com/cas/logout"
        //3.2 或是指定service,在跳转到某个位置。
        String encodedUrl = ConvertUtil.urlEncode(sinoOauthConfig.getServerUrl() + "/index.jsp");
        String result = "redirect:" + sinoOauthConfig.getCasServerUrl() + "/logout?service=" + encodedUrl;
        return result;
    }


    /**
     * 【被动】退出方法
     * @param request
     * @param response
     */
    @RequestMapping(value = {"/passiveLogout"})
    @ResponseBody
    public void passiveLogout(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("passiveLogout");

        SessionUtil.destroySession(request);
        SessionUtil.getSessions();
    }
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

更多的退出参考 integrated-logout.html

# 3、demo程序

下载地址:client-oauth2.zip

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