bug
zhangzeli
2022-01-17 e151c7c3e105358abe1f84f10f09b12e3430915b
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package cn.exrick.xboot.open.controller;
 
import cn.exrick.xboot.core.common.annotation.SystemLog;
import cn.exrick.xboot.core.common.constant.OAuthConstant;
import cn.exrick.xboot.core.common.constant.SecurityConstant;
import cn.exrick.xboot.core.common.enums.LogType;
import cn.exrick.xboot.core.common.exception.XbootException;
import cn.exrick.xboot.core.common.redis.RedisTemplateHelper;
import cn.exrick.xboot.core.common.utils.NameUtil;
import cn.exrick.xboot.core.common.utils.ResultUtil;
import cn.exrick.xboot.core.common.utils.SecurityUtil;
import cn.exrick.xboot.core.common.vo.Result;
import cn.exrick.xboot.core.config.security.SecurityUserDetails;
import cn.exrick.xboot.core.entity.User;
import cn.exrick.xboot.core.service.UserService;
import cn.exrick.xboot.open.entity.Client;
import cn.exrick.xboot.open.service.ClientService;
import cn.exrick.xboot.open.vo.Oauth2TokenInfo;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
 
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
 
/**
 * @author Exrick
 */
@Slf4j
@RestController
@Api(tags = "OAuth2认证接口")
@RequestMapping("/xboot/oauth2")
public class Oauth2Controller {
 
    @Autowired
    private ClientService clientService;
 
    @Autowired
    private UserService userService;
 
    @Autowired
    private SecurityUtil securityUtil;
 
    @Autowired
    private RedisTemplateHelper redisTemplate;
 
    @RequestMapping(value = "/info/{client_id}", method = RequestMethod.GET)
    @ApiOperation(value = "站点基本信息")
    public Result<Object> info(@ApiParam("客户端id") @PathVariable String client_id) {
 
        Client client = getClient(client_id);
 
        Map<String, Object> map = new HashMap<>(16);
        map.put("name", client.getName());
        map.put("homeUri", client.getHomeUri());
        map.put("logo", client.getLogo());
        map.put("autoApprove", client.getAutoApprove());
        return ResultUtil.data(map);
    }
 
    @RequestMapping(value = "/authorize", method = RequestMethod.POST)
    @ApiOperation(value = "认证获取code")
    @SystemLog(description = "认证中心登录", type = LogType.LOGIN)
    public Result<Object> authorize(@ApiParam("用户名") @RequestParam String username,
                                    @ApiParam("密码") @RequestParam String password,
                                    @ApiParam("客户端id") @RequestParam String client_id,
                                    @ApiParam("成功授权后回调地址") @RequestParam String redirect_uri,
                                    @ApiParam("授权类型为code") @RequestParam(required = false, defaultValue = "code") String response_type,
                                    @ApiParam("客户端状态值") @RequestParam String state) {
 
        Client client = getClient(client_id);
 
        if (!client.getRedirectUri().equals(redirect_uri)) {
            return ResultUtil.error("回调地址redirect_uri不正确");
        }
        // 登录认证
        User user;
        if (NameUtil.mobile(username)) {
            user = userService.findByMobile(username);
        } else if (NameUtil.email(username)) {
            user = userService.findByEmail(username);
        } else {
            user = userService.findByUsername(username);
        }
        if (user == null) {
            return ResultUtil.error("用户名不存在");
        }
        if (!new BCryptPasswordEncoder().matches(password, user.getPassword())) {
            return ResultUtil.error("用户密码不正确");
        }
        String accessToken = securityUtil.getToken(user.getUsername(), true);
        // 生成code 5分钟内有效
        String code = IdUtil.simpleUUID();
        // 存入用户及clientId信息
        redisTemplate.set(OAuthConstant.OAUTH_CODE_PRE + code,
                new Gson().toJson(new Oauth2TokenInfo(client_id, user.getUsername())), 5L, TimeUnit.MINUTES);
        Map<String, Object> map = new HashMap<>(16);
        map.put("code", code);
        map.put("redirect_uri", redirect_uri);
        map.put("state", state);
        map.put("accessToken", accessToken);
        // 记录日志
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                new SecurityUserDetails(new User().setUsername(user.getUsername())), null, null);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return ResultUtil.data(map);
    }
 
    @RequestMapping(value = "/token", method = RequestMethod.GET)
    @ApiOperation(value = "获取access_token令牌")
    public Result<Object> token(@ApiParam("授权类型") @RequestParam String grant_type,
                                @ApiParam("客户端id") @RequestParam String client_id,
                                @ApiParam("客户端秘钥") @RequestParam String client_secret,
                                @ApiParam("认证返回的code") @RequestParam(required = false) String code,
                                @ApiParam("刷新token") @RequestParam(required = false) String refresh_token,
                                @ApiParam("成功授权后回调地址") @RequestParam(required = false) String redirect_uri) {
 
        Client client = getClient(client_id);
 
        // 判断clientSecret
        if (!client.getClientSecret().equals(client_secret)) {
            return ResultUtil.error("client_secret不正确");
        }
        Oauth2TokenInfo tokenInfo = null;
        if (OAuthConstant.AUTHORIZATION_CODE.equals(grant_type)) {
            // 判断回调地址
            if (!client.getRedirectUri().equals(redirect_uri)) {
                return ResultUtil.error("回调地址redirect_uri不正确");
            }
            // 判断code 获取用户信息
            String codeValue = redisTemplate.get(OAuthConstant.OAUTH_CODE_PRE + code);
            if (StrUtil.isBlank(codeValue)) {
                return ResultUtil.error("code已过期");
            }
            tokenInfo = new Gson().fromJson(codeValue, Oauth2TokenInfo.class);
            if (!tokenInfo.getClientId().equals(client_id)) {
                return ResultUtil.error("code不正确");
            }
        } else if (OAuthConstant.REFRESH_TOKEN.equals(grant_type)) {
            // 从refreshToken中获取用户信息
            String refreshTokenValue = redisTemplate.get(OAuthConstant.OAUTH_TOKEN_INFO_PRE + refresh_token);
            if (StrUtil.isBlank(refreshTokenValue)) {
                return ResultUtil.error("refresh_token已过期");
            }
            tokenInfo = new Gson().fromJson(refreshTokenValue, Oauth2TokenInfo.class);
            if (!tokenInfo.getClientId().equals(client_id)) {
                return ResultUtil.error("refresh_token不正确");
            }
        } else {
            return ResultUtil.error("授权类型grant_type不正确");
        }
 
        String token = null, refreshToken = null;
        Long expiresIn = null;
        String tokenKey = OAuthConstant.OAUTH_TOKEN_PRE + tokenInfo.getUsername() + ":" + client_id,
                refreshKey = OAuthConstant.OAUTH_REFRESH_TOKEN_PRE + tokenInfo.getUsername() + ":" + client_id;
        if (OAuthConstant.AUTHORIZATION_CODE.equals(grant_type)) {
            // 生成token模式
            String oldToken = redisTemplate.get(tokenKey);
            String oldRefreshToken = redisTemplate.get(refreshKey);
            if (StrUtil.isNotBlank(oldToken) && StrUtil.isNotBlank(oldRefreshToken)) {
                // 旧token
                token = oldToken;
                refreshToken = oldRefreshToken;
                expiresIn = redisTemplate.getExpire(OAuthConstant.OAUTH_TOKEN_INFO_PRE + token, TimeUnit.SECONDS);
            } else {
                // 新生成 30天过期
                String newToken = IdUtil.simpleUUID();
                String newRefreshToken = IdUtil.simpleUUID();
                redisTemplate.set(tokenKey, newToken, 30L, TimeUnit.DAYS);
                redisTemplate.set(refreshKey, newRefreshToken, 30L, TimeUnit.DAYS);
                // 新token中存入用户信息
                redisTemplate.set(OAuthConstant.OAUTH_TOKEN_INFO_PRE + newToken, new Gson().toJson(tokenInfo), 30L, TimeUnit.DAYS);
                redisTemplate.set(OAuthConstant.OAUTH_TOKEN_INFO_PRE + newRefreshToken, new Gson().toJson(tokenInfo), 30L, TimeUnit.DAYS);
                token = newToken;
                refreshToken = newRefreshToken;
                expiresIn = redisTemplate.getExpire(OAuthConstant.OAUTH_TOKEN_INFO_PRE + token, TimeUnit.SECONDS);
            }
        } else if (OAuthConstant.REFRESH_TOKEN.equals(grant_type)) {
            // 刷新token模式 生成新token 30天过期
            String newToken = IdUtil.simpleUUID();
            String newRefreshToken = IdUtil.simpleUUID();
            redisTemplate.set(tokenKey, newToken, 30L, TimeUnit.DAYS);
            redisTemplate.set(refreshKey, newRefreshToken, 30L, TimeUnit.DAYS);
            // 新token中存入用户信息
            redisTemplate.set(OAuthConstant.OAUTH_TOKEN_INFO_PRE + newToken, new Gson().toJson(tokenInfo), 30L, TimeUnit.DAYS);
            redisTemplate.set(OAuthConstant.OAUTH_TOKEN_INFO_PRE + newRefreshToken, new Gson().toJson(tokenInfo), 30L, TimeUnit.DAYS);
            token = newToken;
            refreshToken = newRefreshToken;
            expiresIn = redisTemplate.getExpire(OAuthConstant.OAUTH_TOKEN_INFO_PRE + token, TimeUnit.SECONDS);
            // 旧refreshToken过期
            redisTemplate.delete(OAuthConstant.OAUTH_TOKEN_INFO_PRE + refresh_token);
        }
 
        Map<String, Object> map = new HashMap<>(16);
        map.put("access_token", token);
        map.put("expires_in", expiresIn);
        map.put("refresh_token", refreshToken);
        return ResultUtil.data(map);
    }
 
    @RequestMapping(value = "/authorized", method = RequestMethod.POST)
    @ApiOperation(value = "已认证过获取code/单点登录实现")
    public Result<Object> authorized(@ApiParam("客户端id") @RequestParam String client_id,
                                     @ApiParam("成功授权后回调地址") @RequestParam String redirect_uri,
                                     @ApiParam("客户端状态值") @RequestParam String state) {
 
        Client client = getClient(client_id);
 
        // 判断回调地址
        if (!client.getRedirectUri().equals(redirect_uri)) {
            return ResultUtil.error("回调地址redirect_uri不正确");
        }
 
        User user = securityUtil.getCurrUser();
 
        // 生成code 5分钟内有效
        String code = IdUtil.simpleUUID();
        redisTemplate.set(OAuthConstant.OAUTH_CODE_PRE + code,
                new Gson().toJson(new Oauth2TokenInfo(client_id, user.getUsername())), 5L, TimeUnit.MINUTES);
        Map<String, Object> map = new HashMap<>(16);
        map.put("code", code);
        map.put("redirect_uri", redirect_uri);
        map.put("state", state);
        return ResultUtil.data(map);
    }
 
    @RequestMapping(value = "/logout", method = RequestMethod.POST)
    @ApiOperation(value = "退出登录(内部信任站点使用)")
    public Result<Object> logout() {
 
        User user = securityUtil.getCurrUser();
 
        // 删除当前用户登录accessToken
        String token = redisTemplate.get(SecurityConstant.USER_TOKEN + user.getUsername());
        redisTemplate.delete(token);
        redisTemplate.delete(SecurityConstant.USER_TOKEN + user.getUsername());
        // 删除当前用户授权第三方应用的access_token
        redisTemplate.deleteByPattern(OAuthConstant.OAUTH_TOKEN_PRE + user.getUsername() + ":*");
        redisTemplate.deleteByPattern(OAuthConstant.OAUTH_REFRESH_TOKEN_PRE + user.getUsername() + ":*");
        return ResultUtil.data(null);
    }
 
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    @ApiOperation(value = "获取用户信息")
    public Result<Object> user(@ApiParam("令牌") @RequestParam String access_token) {
 
        String tokenValue = redisTemplate.get(OAuthConstant.OAUTH_TOKEN_INFO_PRE + access_token);
        if (StrUtil.isBlank(tokenValue)) {
            return ResultUtil.error("access_token已过期失效");
        }
        Oauth2TokenInfo tokenInfo = new Gson().fromJson(tokenValue, Oauth2TokenInfo.class);
        User user = userService.findByUsername(tokenInfo.getUsername());
        if (user == null) {
            return ResultUtil.error("用户信息不存在");
        }
        Map<String, Object> map = new HashMap<>(16);
        map.put("username", tokenInfo.getUsername());
        map.put("avatar", user.getAvatar());
        map.put("sex", user.getSex());
        return ResultUtil.data(map);
    }
 
    private Client getClient(String client_id) {
 
        Client client = clientService.get(client_id);
        if (client == null) {
            throw new XbootException("客户端client_id不存在");
        }
        return client;
    }
}