10.3 AcApp与Web端的第三方授权登录

master
barney 2 years ago
parent 43df2b3f8b
commit e03a71776a
  1. 47
      acapp/src/App.vue
  2. 1
      acapp/src/store/user.js
  3. 14
      backendCloud/backend/pom.xml
  4. 7
      backendCloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java
  5. 31
      backendCloud/backend/src/main/java/com/kob/backend/controller/user/account/acwing/AcappController.java
  6. 27
      backendCloud/backend/src/main/java/com/kob/backend/controller/user/account/acwing/WebController.java
  7. 1
      backendCloud/backend/src/main/java/com/kob/backend/pojo/User.java
  8. 2
      backendCloud/backend/src/main/java/com/kob/backend/service/impl/user/account/RegisterServiceImpl.java
  9. 136
      backendCloud/backend/src/main/java/com/kob/backend/service/impl/user/account/acwing/AcappServiceImpl.java
  10. 138
      backendCloud/backend/src/main/java/com/kob/backend/service/impl/user/account/acwing/WebServiceImpl.java
  11. 38
      backendCloud/backend/src/main/java/com/kob/backend/service/impl/user/account/acwing/utils/HttpClientUtil.java
  12. 8
      backendCloud/backend/src/main/java/com/kob/backend/service/user/account/acwing/AcappService.java
  13. 8
      backendCloud/backend/src/main/java/com/kob/backend/service/user/account/acwing/WebService.java
  14. 5
      backendCloud/backend/src/main/resources/application.properties
  15. 3
      web/src/components/NavBar.vue
  16. 11
      web/src/router/index.js
  17. 3
      web/src/views/record/RecordIndexView.vue
  18. 42
      web/src/views/user/account/UserAcWingCodeIndex.vue
  19. 18
      web/src/views/user/account/UserLoginView.vue

@ -17,6 +17,7 @@ import RanklistIndexView from "./views/ranklist/RanklistIndexView.vue";
import RecordIndexView from "@/views/record/RecordIndexView.vue";
import RecordContentView from "@/views/record/RecordContentView.vue";
import UserBotIndexView from "@/views/user/bot/UserBotIndexView.vue";
import $ from 'jquery';
export default {
components: {
@ -29,23 +30,35 @@ export default {
},
setup() {
const store = useStore();
// localStoragetoken
const jwt_token =
"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0YTMyOTdhYzE0YmY0ZDVkYmU2ODAxZDVkNTk3ODhiYyIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY2MjYyMTAwNCwiZXhwIjoxNjYzODMwNjA0fQ.rKZG8n3U_U50Aql25nB2k27cZWtxSpE4__a8bnSP3Fs";
if (jwt_token) {
store.commit("updateToken", jwt_token);
store.dispatch("getInfo", {
//
success() {
store.commit("updatePullingInfo", false);
},
error() {
store.commit("updatePullingInfo", false);
},
});
} else {
store.commit("updatePullingInfo", false);
}
$.ajax({
url: "https://kob.bnblogs.cc/api/user/account/acwing/acapp/apply_code/",
type: "GET",
success: resp => {
if (resp.result === "success") {
store.state.user.AcWingOS.api.oauth2.authorize(resp.appid, resp.redirect_uri, resp.scope, resp.state, resp => {
if (resp.result === "success") {
const jwt_token = resp.jwt_token;
store.commit("updateToken", jwt_token);
store.dispatch("getInfo", {
//
success() {
store.commit("updatePullingInfo", false);
},
error() {
store.commit("updatePullingInfo", false);
},
});
}else {
store.state.user.AcWingOS.api.window.close(); // acwing
}
});
}
else {
store.state.user.AcWingOS.api.window.close(); // acwing
}
}
});
},
};
</script>

@ -8,6 +8,7 @@ export default {
token: "",
is_login: false, // 默认未登录状态
pulling_info: true, // 是否正在拉取信息
AcWingOS: "AcWingOS",
},
getters: {
},

@ -99,6 +99,20 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.3</version>
</dependency>
</dependencies>
<build>

@ -43,7 +43,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/user/account/token/", "/api/user/account/register/").permitAll()
.antMatchers("/api/user/account/token/",
"/api/user/account/register/",
"/api/user/account/acwing/web/apply_code/",
"/api/user/account/acwing/web/receive_code/",
"/api/user/account/acwing/acapp/apply_code/",
"/api/user/account/acwing/acapp/receive_code/").permitAll()
.antMatchers("/pk/start/game/").hasIpAddress("127.0.0.1")
.antMatchers("/pk/receive/bot/move/").hasIpAddress("127.0.0.1")
.antMatchers(HttpMethod.OPTIONS).permitAll()

@ -0,0 +1,31 @@
package com.kob.backend.controller.user.account.acwing;
import com.alibaba.fastjson.JSONObject;
import com.kob.backend.service.user.account.acwing.AcappService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class AcappController {
@Autowired
private AcappService acappService;
@GetMapping("/api/user/account/acwing/acapp/apply_code/")
public JSONObject applyCode() {
return acappService.applyCode();
}
@GetMapping("/api/user/account/acwing/acapp/receive_code/")
public JSONObject receiveCode(@RequestParam Map<String,String> data) {
String code = data.get("code");
String state = data.get("state");
return acappService.receiveCode(code,state);
}
}

@ -0,0 +1,27 @@
package com.kob.backend.controller.user.account.acwing;
import com.alibaba.fastjson.JSONObject;
import com.kob.backend.service.user.account.acwing.WebService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class WebController {
@Autowired
private WebService webService;
@GetMapping("/api/user/account/acwing/web/apply_code/")
public JSONObject applyCode() {
return webService.applyCode();
}
@GetMapping("/api/user/account/acwing/web/receive_code/")
public JSONObject receiveCode(@RequestParam Map<String,String> data) {
String code = data.get("code");
String state = data.get("state");
return webService.receiveCode(code,state);
}
}

@ -26,4 +26,5 @@ public class User {
private String password;
private String photo;
private Integer rating;
private String openid;
}

@ -76,7 +76,7 @@ public class RegisterServiceImpl implements RegisterService {
// 将用户密码加密
String encodedPassword = passwordEncoder.encode(password);
String photo = "https://cdn.acwing.com/media/user/profile/photo/41648_lg_8d1ad446b6.jpg";
User user = new User(null,username,encodedPassword,photo,1500);
User user = new User(null,username,encodedPassword,photo,1500,null);
// 将用户数据存入数据库
userMapper.insert(user);

@ -0,0 +1,136 @@
package com.kob.backend.service.impl.user.account.acwing;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.user.account.acwing.utils.HttpClientUtil;
import com.kob.backend.service.user.account.acwing.AcappService;
import com.kob.backend.utils.JwtUtil;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
@Service
public class AcappServiceImpl implements AcappService {
private static final String appId = "3357";
private static final String appSecret = "f343ff7eeaa649d2a257bffac0a9323a";
private static final String redirectUri = "https://kob.bnblogs.cc/api/user/account/acwing/acapp/receive_code/";
private static final String applyAccessTokenUrl = "https://www.acwing.com/third_party/api/oauth2/access_token/";
private static final String getUserInfoUrl = "https://www.acwing.com/third_party/api/meta/identity/getinfo/";
private static final Random random = new Random();
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
public JSONObject applyCode() {
JSONObject resp = new JSONObject();
resp.put("appid",appId);
try {
resp.put("redirect_uri", URLEncoder.encode(redirectUri,"UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
resp.put("result","failed");
return resp;
}
resp.put("scope","userinfo");
StringBuilder state = new StringBuilder();
for (int i = 0; i < 10; i++) {
state.append((char)(random.nextInt(10) + '0')); // 十位随机数字字符串
}
resp.put("state",state.toString());
resp.put("result","success");
redisTemplate.opsForValue().set(state.toString(),"true"); // 将state值存入redis
redisTemplate.expire(state.toString(), Duration.ofMinutes(10)); // 设置过期时间为10分钟
return resp;
}
@Override
public JSONObject receiveCode(String code, String state) {
JSONObject resp = new JSONObject();
resp.put("result","failed");
if (code == null || state == null) return resp; // 传入的code和state为空
if (Boolean.FALSE.equals(redisTemplate.hasKey(state))) return resp; // redis中没有该state
redisTemplate.delete(state); // 删除该state,只使用一次
// 获取accessToken
List<NameValuePair> nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("appid",appId));
nameValuePairs.add(new BasicNameValuePair("secret",appSecret));
nameValuePairs.add(new BasicNameValuePair("code",code));
String getString = HttpClientUtil.get(applyAccessTokenUrl,nameValuePairs); // 请求结果存入一个字符串
if (getString == null) return resp;
JSONObject getResp = JSONObject.parseObject(getString);
String accessToken = getResp.getString("access_token");
String openId = getResp.getString("openid");
if (accessToken == null || openId == null) return resp;
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("openid",openId);
List<User> users = userMapper.selectList(qw); // 查询满足openid的用户是否存在
if (!users.isEmpty()) {
User user = users.get(0);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result","success");
resp.put("jwt_token",jwt);
return resp;
}
// 如果不存在说明该用户没有登录过,需要根据上面的accessToken和openid向Acwing请求用户信息
nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("access_token",accessToken));
nameValuePairs.add(new BasicNameValuePair("openid",openId));
getString = HttpClientUtil.get(getUserInfoUrl,nameValuePairs); // 将请求结果存入字符串
if (getString == null) return resp;
getResp = JSONObject.parseObject(getString);
// 获得用户的用户名和头像
String username = getResp.getString("username");
String photo = getResp.getString("photo");
if (username == null || photo == null) return resp;
// 避免新出现的用户重名
for (int i = 0; i < 100; i++) { // 随机在后面添加以为数字
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
// 没有重名,直接退出
if (userMapper.selectList(queryWrapper).isEmpty()) break;
username += (char)(random.nextInt(10) + '0');
if (i == 99) return resp; // 小概率事件,太逆天,直接返回
}
// 根据获得的信息建立一个用户
User user = new User(null,username,null,photo,null,openId);
userMapper.insert(user);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result","success");
resp.put("jwt_token",jwt);
return resp;
}
}

@ -0,0 +1,138 @@
package com.kob.backend.service.impl.user.account.acwing;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.user.account.acwing.utils.HttpClientUtil;
import com.kob.backend.service.user.account.acwing.WebService;
import com.kob.backend.utils.JwtUtil;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
@Service
public class WebServiceImpl implements WebService {
private static final String appId = "3357";
private static final String appSecret = "f343ff7eeaa649d2a257bffac0a9323a";
private static final String redirectUri = "https://kob.bnblogs.cc/user/account/acwing/web/receive_code/";
private static final String applyAccessTokenUrl = "https://www.acwing.com/third_party/api/oauth2/access_token/";
private static final String getUserInfoUrl = "https://www.acwing.com/third_party/api/meta/identity/getinfo/";
private static final Random random = new Random();
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
public JSONObject applyCode() {
JSONObject resp = new JSONObject();
String encodeUrl="";
try {
encodeUrl = URLEncoder.encode(redirectUri,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
resp.put("result","failed");
return resp;
}
StringBuilder state = new StringBuilder();
for (int i = 0; i < 10; i++) {
state.append((char)(random.nextInt(10) + '0')); // 十位随机数字字符串
}
resp.put("result","success");
redisTemplate.opsForValue().set(state.toString(),"true"); // 将state值存入redis
redisTemplate.expire(state.toString(), Duration.ofMinutes(10)); // 设置过期时间为10分钟
String applyCodeUrl = "https://www.acwing.com/third_party/api/oauth2/web/authorize/?appid=" + appId
+ "&redirect_uri=" + encodeUrl
+ "&scope=userinfo"
+ "&state=" + state;
resp.put("apply_code_url",applyCodeUrl);
return resp;
}
@Override
public JSONObject receiveCode(String code, String state) {
JSONObject resp = new JSONObject();
resp.put("result","failed");
if (code == null || state == null) return resp; // 传入的code和state为空
if (Boolean.FALSE.equals(redisTemplate.hasKey(state))) return resp; // redis中没有该state
redisTemplate.delete(state); // 删除该state,只使用一次
// 获取accessToken
List<NameValuePair> nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("appid",appId));
nameValuePairs.add(new BasicNameValuePair("secret",appSecret));
nameValuePairs.add(new BasicNameValuePair("code",code));
String getString = HttpClientUtil.get(applyAccessTokenUrl,nameValuePairs); // 请求结果存入一个字符串
if (getString == null) return resp;
JSONObject getResp = JSONObject.parseObject(getString);
String accessToken = getResp.getString("access_token");
String openId = getResp.getString("openid");
if (accessToken == null || openId == null) return resp;
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("openid",openId);
List<User> users = userMapper.selectList(qw); // 查询满足openid的用户是否存在
if (!users.isEmpty()) {
User user = users.get(0);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result","success");
resp.put("jwt_token",jwt);
return resp;
}
// 如果不存在说明该用户没有登录过,需要根据上面的accessToken和openid向Acwing请求用户信息
nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("access_token",accessToken));
nameValuePairs.add(new BasicNameValuePair("openid",openId));
getString = HttpClientUtil.get(getUserInfoUrl,nameValuePairs); // 将请求结果存入字符串
if (getString == null) return resp;
getResp = JSONObject.parseObject(getString);
// 获得用户的用户名和头像
String username = getResp.getString("username");
String photo = getResp.getString("photo");
if (username == null || photo == null) return resp;
// 避免新出现的用户重名
for (int i = 0; i < 100; i++) { // 随机在后面添加以为数字
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
// 没有重名,直接退出
if (userMapper.selectList(queryWrapper).isEmpty()) break;
username += (char)(random.nextInt(10) + '0');
if (i == 99) return resp; // 小概率事件,太逆天,直接返回
}
// 根据获得的信息建立一个用户
User user = new User(null,username,null,photo,null,openId);
userMapper.insert(user);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result","success");
resp.put("jwt_token",jwt);
return resp;
}
}

@ -0,0 +1,38 @@
package com.kob.backend.service.impl.user.account.acwing.utils;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
public class HttpClientUtil {
public static String get(String url, List<NameValuePair> params) {
URIBuilder uriBuilder = null;
try {
uriBuilder = new URIBuilder(url);
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
uriBuilder.setParameters(params);
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(uriBuilder.build());
CloseableHttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity);
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
return null;
}
}
}

@ -0,0 +1,8 @@
package com.kob.backend.service.user.account.acwing;
import com.alibaba.fastjson.JSONObject;
public interface AcappService {
JSONObject applyCode();
JSONObject receiveCode(String code,String state);
}

@ -0,0 +1,8 @@
package com.kob.backend.service.user.account.acwing;
import com.alibaba.fastjson.JSONObject;
public interface WebService {
JSONObject applyCode();
JSONObject receiveCode(String code,String state);
}

@ -2,4 +2,7 @@ server.port=4000
spring.datasource.username=root
spring.datasource.password=zfp251217
spring.datasource.url=jdbc:mysql://101.33.213.197:3307/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.redis.database=0
spring.redis.host=101.33.213.197
spring.redis.port=6379

@ -2,6 +2,9 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<router-link class="navbar-brand" :to="{name: 'home'}">King Of Bots</router-link>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">

@ -7,6 +7,9 @@ import UserBotIndexView from '../views/user/bot/UserBotIndexView'
import NotFound from '../views/error/NotFound'
import UserLoginView from "@/views/user/account/UserLoginView"
import UserRegisterView from "@/views/user/account/UserRegisterView"
import UserAcWingCodeIndex from "@/views/user/account/UserAcWingCodeIndex"
import store from '@/store'
const routes = [
@ -80,8 +83,14 @@ const routes = [
meta: {
requestAuth: false,
},
},{
path: "/user/account/acwing/web/receive_code/",
name: "Acwing",
component: UserAcWingCodeIndex,
meta: {
requestAuth: false,
},
},
{
path: "/:catchAll(.*)",
redirect: "/404/"

@ -111,6 +111,7 @@ export default {
"Authorization": "Bearer " + store.state.user.token, //
},
success(resp){
console.log(resp);
records.value = resp.records;
total_records = resp.records_count;
updatePages();
@ -168,8 +169,6 @@ export default {
recordId,
}
});
break;
}
}

@ -0,0 +1,42 @@
<template>
<div></div>
</template>
<script>
import $ from 'jquery';
import router from '@/router/index'
import { useRoute } from "vue-router"
import { useStore } from "vuex";
export default {
setup() {
const myRouter = useRoute();
const store= useStore();
$.ajax({
url: "https://kob.bnblogs.cc/api/user/account/acwing/web/receive_code/",
type: "GET",
data: {
code: myRouter.query.code,
state: myRouter.query.state,
},
success: resp => {
if (resp.result === "success") {
console.log(resp);
// jwt_token
localStorage.setItem("jwt_token",resp.jwt_token);
store.commit("updateToken",resp.jwt_token);
router.push({ name:"home" }); //
store.commit("updatePullingInfo",false);
}else { //
router.push({ name:"register" });
}
}
});
}
}
</script>
<style scoped>
</style>

@ -22,6 +22,10 @@
<div class="error-messsage"> {{error_msg}} </div>
<button type="submit" class="btn btn-primary btn-sm">登录</button>
</form>
<div style="text-align: center;margin-top: 14px;cursor: pointer" @click="acwing_login">
<img width="40" src="https://cdn.acwing.com/media/article/image/2022/09/06/1_32f001fd2d-acwing_logo.png" alt="">
<div>AcWing一键登录</div>
</div>
</div>
</div>
</ContentField>
@ -32,6 +36,7 @@ import ContentField from "@/components/ContentField.vue";
import { useStore } from "vuex";
import { ref } from "vue";
import router from '@/router/index';
import $ from 'jquery';
export default {
components: {
@ -82,11 +87,24 @@ export default {
});
}
const acwing_login = () => {
$.ajax({
url: "https://kob.bnblogs.cc/api/user/account/acwing/web/apply_code/",
type: "GET",
success: resp => {
if (resp.result === "success") {
window.location.replace(resp.apply_code_url);
}
}
});
}
return {
username,
password,
error_msg,
login,
acwing_login,
}
}
};

Loading…
Cancel
Save