From 69d9cae67e2de255ea539b69f3b6fc3e2afe97e1 Mon Sep 17 00:00:00 2001 From: XSHA AI Date: Fri, 24 Oct 2025 06:03:39 +0000 Subject: [PATCH 1/2] AI generated changes for conversation 35 --- pom.xml | 15 ++ .../course/config/WebSecurityConfig.java | 51 +++++ .../course/controller/LoginController.java | 25 ++- .../course/controller/PageController.java | 44 ++++ .../youfool/course/entity/SysUser.java | 14 +- .../security/OAuth2LoginFailureHandler.java | 31 +++ .../security/OAuth2LoginSuccessHandler.java | 96 +++++++++ .../course/service/SysUserService.java | 16 ++ .../service/impl/SysUserServiceImpl.java | 15 ++ src/main/resources/application.yml | 24 +++ src/main/resources/templates/index.html | 100 +++++++++ src/main/resources/templates/login.html | 203 ++++++++++++++++++ 12 files changed, 628 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java create mode 100644 src/main/java/com/chinaweal/youfool/course/controller/PageController.java create mode 100644 src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java create mode 100644 src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java create mode 100644 src/main/resources/templates/index.html create mode 100644 src/main/resources/templates/login.html diff --git a/pom.xml b/pom.xml index 4191414..b7eda40 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,21 @@ 1.18.42 provided + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + org.springframework.boot + spring-boot-starter-web + diff --git a/src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java b/src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java new file mode 100644 index 0000000..b340e70 --- /dev/null +++ b/src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java @@ -0,0 +1,51 @@ +package com.chinaweal.youfool.course.config; + +import com.chinaweal.youfool.course.security.OAuth2LoginFailureHandler; +import com.chinaweal.youfool.course.security.OAuth2LoginSuccessHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; + +/** + * Web安全配置 + * + * @author lroyia + * @since 2025/10/24 + **/ +@Configuration +@EnableWebSecurity +public class WebSecurityConfig { + + private final OAuth2LoginSuccessHandler oauth2LoginSuccessHandler; + private final OAuth2LoginFailureHandler oauth2LoginFailureHandler; + + public WebSecurityConfig(OAuth2LoginSuccessHandler oauth2LoginSuccessHandler, + OAuth2LoginFailureHandler oauth2LoginFailureHandler) { + this.oauth2LoginSuccessHandler = oauth2LoginSuccessHandler; + this.oauth2LoginFailureHandler = oauth2LoginFailureHandler; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/login", "/error", "/webjars/**", "/css/**", "/js/**").permitAll() + .requestMatchers("/user/auth/**").permitAll() + .anyRequest().authenticated() + ) + .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") + .successHandler(oauth2LoginSuccessHandler) + .failureHandler(oauth2LoginFailureHandler) + ) + .csrf(csrf -> csrf.disable()) + .formLogin(form -> form + .loginPage("/login") + .permitAll() + ); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/controller/LoginController.java b/src/main/java/com/chinaweal/youfool/course/controller/LoginController.java index 2bae687..b9aecaf 100644 --- a/src/main/java/com/chinaweal/youfool/course/controller/LoginController.java +++ b/src/main/java/com/chinaweal/youfool/course/controller/LoginController.java @@ -9,7 +9,10 @@ import com.chinaweal.youfool.framework.springboot.rest.RestResult; import com.chinaweal.youfool.framework.springboot.rest.ResultCode; import com.chinaweal.youfool.framework.springboot.user.entity.UserBase; import com.chinaweal.youfool.course.common.constants.SessionConstants; +import com.chinaweal.youfool.course.entity.SysUser; +import com.chinaweal.youfool.course.service.SysUserService; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -30,6 +33,9 @@ import java.util.Set; @RequestMapping("/user/auth") public class LoginController extends BaseController { + @Autowired + private SysUserService sysUserService; + /** * 登录接口 * @@ -53,17 +59,26 @@ public class LoginController extends BaseController { } } - // TODO:与数据库匹配校验,具体按用户信息表 + // 查询用户信息 + SysUser sysUser = sysUserService.getUserByUsername(username); + if (sysUser == null || !sysUser.getPassword().equals(password)) { + return RestResult.error(ResultCode.BUSINESS_LOGIC_ERROR, "用户名或密码错误"); + } - // 匹配成功的话,登记登录信息 TODO:这里的userId是用户唯一号,应根据实际的数据库信息进行替换 - StpUtil.login("userId"); + // 匹配成功的话,登记登录信息 + StpUtil.login(sysUser.getUserId()); SaSession session = StpUtil.getSession(); - // 将用户信息存储至session TODO:登录信息存到这里 + // 将用户信息存储至session UserBase userBase = new UserBase(); + userBase.setUsername(sysUser.getUsername()); + userBase.setEmail(sysUser.getEmail()); + userBase.setAvatar(sysUser.getAvatar()); + userBase.setId(sysUser.getUserId()); + Set permissionSet = new HashSet<>(); permissionSet.add("admin"); userBase.setPermission(permissionSet); - session.set("user", userBase); + session.set(SessionConstants.USER_KEY, userBase); return RestResult.ok(); } diff --git a/src/main/java/com/chinaweal/youfool/course/controller/PageController.java b/src/main/java/com/chinaweal/youfool/course/controller/PageController.java new file mode 100644 index 0000000..27e79f5 --- /dev/null +++ b/src/main/java/com/chinaweal/youfool/course/controller/PageController.java @@ -0,0 +1,44 @@ +package com.chinaweal.youfool.course.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * 页面控制器 + * + * @author lroyia + * @since 2025/10/24 + **/ +@Controller +public class PageController { + + /** + * 登录页面 + * + * @return 登录页面 + */ + @GetMapping("/login") + public String loginPage() { + return "login"; + } + + /** + * 首页 + * + * @return 首页 + */ + @GetMapping("/") + public String index() { + return "index"; + } + + /** + * 首页 + * + * @return 首页 + */ + @GetMapping("/index") + public String indexPage() { + return "index"; + } +} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/entity/SysUser.java b/src/main/java/com/chinaweal/youfool/course/entity/SysUser.java index 010d210..0ade793 100644 --- a/src/main/java/com/chinaweal/youfool/course/entity/SysUser.java +++ b/src/main/java/com/chinaweal/youfool/course/entity/SysUser.java @@ -81,6 +81,15 @@ public class SysUser implements Serializable { @Schema(description = "邮箱", example = "user@example.com") private String email; + /** + * 密码 + * + *

用户的登录密码,存储时应该加密处理。

+ */ + @TableField("password") + @Schema(description = "密码", example = "encrypted_password") + private String password; + /** * Gitea Open ID * @@ -114,16 +123,18 @@ public class SysUser implements Serializable { * @param name 姓名 * @param avatar 头像 * @param email 邮箱 + * @param password 密码 * @param giteaOpenId Gitea Open ID */ public SysUser(String userId, String username, String nickname, String name, - String avatar, String email, String giteaOpenId) { + String avatar, String email, String password, String giteaOpenId) { this.userId = userId; this.username = username; this.nickname = nickname; this.name = name; this.avatar = avatar; this.email = email; + this.password = password; this.giteaOpenId = giteaOpenId; } @@ -136,6 +147,7 @@ public class SysUser implements Serializable { ", name='" + name + '\'' + ", avatar='" + avatar + '\'' + ", email='" + email + '\'' + + ", password='" + password + '\'' + ", giteaOpenId='" + giteaOpenId + '\'' + '}'; } diff --git a/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java b/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java new file mode 100644 index 0000000..1bd727d --- /dev/null +++ b/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java @@ -0,0 +1,31 @@ +package com.chinaweal.youfool.course.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * OAuth2登录失败处理器 + * + * @author lroyia + * @since 2025/10/24 + **/ +@Slf4j +@Component +public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + log.error("OAuth2登录失败: {}", exception.getMessage()); + + // 重定向到登录页面,带上错误信息 + response.sendRedirect("/course/login?error=true&message=" + java.net.URLEncoder.encode(exception.getMessage(), "UTF-8")); + } +} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java b/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java new file mode 100644 index 0000000..2030e3c --- /dev/null +++ b/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java @@ -0,0 +1,96 @@ +package com.chinaweal.youfool.course.security; + +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.StpUtil; +import com.chinaweal.youfool.framework.springboot.user.entity.UserBase; +import com.chinaweal.youfool.course.common.constants.SessionConstants; +import com.chinaweal.youfool.course.entity.SysUser; +import com.chinaweal.youfool.course.service.SysUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * OAuth2登录成功处理器 + * + * @author lroyia + * @since 2025/10/24 + **/ +@Slf4j +@Component +public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { + + @Autowired + private SysUserService sysUserService; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal(); + Map attributes = oauth2User.getAttributes(); + + // 获取Gitea用户信息 + String username = (String) attributes.get("username"); + String email = (String) attributes.get("email"); + String avatarUrl = (String) attributes.get("avatar_url"); + Integer id = (Integer) attributes.get("id"); + + log.info("Gitea用户登录成功: username={}, email={}, id={}", username, email, id); + + // 生成Gitea Open ID + String giteaOpenId = String.valueOf(id); + + // 检查用户是否已存在 + SysUser sysUser = sysUserService.getUserByGiteaOpenId(giteaOpenId); + + // 如果用户不存在,则创建新用户 + if (sysUser == null) { + sysUser = new SysUser(); + sysUser.setUsername(username); + sysUser.setNickname(username); + sysUser.setEmail(email); + sysUser.setAvatar(avatarUrl); + sysUser.setGiteaOpenId(giteaOpenId); + sysUserService.save(sysUser); + } else { + // 更新用户信息 + sysUser.setEmail(email); + sysUser.setAvatar(avatarUrl); + sysUserService.updateById(sysUser); + } + + // 使用Sa-Token登录 + StpUtil.login(sysUser.getUserId()); + SaSession session = StpUtil.getSession(); + + // 构建用户信息 + UserBase userBase = new UserBase(); + userBase.setUsername(sysUser.getUsername()); + userBase.setEmail(sysUser.getEmail()); + userBase.setAvatarUrl(sysUser.getAvatar()); + userBase.setId(sysUser.getUserId()); + + // 设置权限 + Set permissionSet = new HashSet<>(); + permissionSet.add("user"); + userBase.setPermission(permissionSet); + + // 将用户信息存储至session + session.set(SessionConstants.USER_KEY, userBase); + + // 重定向到首页 + response.sendRedirect("/course/"); + } +} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/service/SysUserService.java b/src/main/java/com/chinaweal/youfool/course/service/SysUserService.java index 6e64313..5c94dde 100644 --- a/src/main/java/com/chinaweal/youfool/course/service/SysUserService.java +++ b/src/main/java/com/chinaweal/youfool/course/service/SysUserService.java @@ -22,4 +22,20 @@ import com.chinaweal.youfool.course.entity.SysUser; * @since 1.0.0 */ public interface SysUserService extends IService { + + /** + * 根据用户名获取用户信息 + * + * @param username 用户名 + * @return 用户信息 + */ + SysUser getUserByUsername(String username); + + /** + * 根据Gitea Open ID获取用户信息 + * + * @param giteaOpenId Gitea Open ID + * @return 用户信息 + */ + SysUser getUserByGiteaOpenId(String giteaOpenId); } \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/service/impl/SysUserServiceImpl.java b/src/main/java/com/chinaweal/youfool/course/service/impl/SysUserServiceImpl.java index f93e41c..7108b05 100644 --- a/src/main/java/com/chinaweal/youfool/course/service/impl/SysUserServiceImpl.java +++ b/src/main/java/com/chinaweal/youfool/course/service/impl/SysUserServiceImpl.java @@ -1,5 +1,6 @@ package com.chinaweal.youfool.course.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.chinaweal.youfool.course.entity.SysUser; import com.chinaweal.youfool.course.mapper.SysUserMapper; @@ -28,4 +29,18 @@ import org.springframework.stereotype.Service; */ @Service public class SysUserServiceImpl extends ServiceImpl implements SysUserService { + + @Override + public SysUser getUserByUsername(String username) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getUsername, username); + return getOne(queryWrapper); + } + + @Override + public SysUser getUserByGiteaOpenId(String giteaOpenId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getGiteaOpenId, giteaOpenId); + return getOne(queryWrapper); + } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index db9bbd2..47bb0de 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,30 @@ spring: active: dev application: name: youfool-lesson + # Thymeleaf配置 + thymeleaf: + cache: false + prefix: classpath:/templates/ + suffix: .html + encoding: UTF-8 + mode: HTML + # OAuth2配置 + security: + oauth2: + client: + registration: + gitea: + client-id: ${GITEA_CLIENT_ID:your-gitea-client-id} + client-secret: ${GITEA_CLIENT_SECRET:your-gitea-client-secret} + authorization-grant-type: authorization_code + redirect-uri: ${BASE_URL:http://localhost:8080}/course/login/oauth2/code/gitea + scope: read:user + provider: + gitea: + authorization-uri: ${GITEA_AUTH_URL:https://gitea.com/login/oauth/authorize} + token-uri: ${GITEA_TOKEN_URL:https://gitea.com/login/oauth/access_token} + user-info-uri: ${GITEA_USER_URL:https://gitea.com/api/v1/user} + user-name-attribute: username datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000..e93299d --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,100 @@ + + + + + + 首页 + + + +
+

课程管理系统

+ +
+ +
+
+

欢迎来到课程管理系统

+

+ 这里是一个基于Spring Boot + Thymeleaf + Gitea OAuth2的课程管理系统。 + 您可以使用用户名密码登录,也可以使用Gitea账号进行第三方登录。 +

+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..78ecd8b --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,203 @@ + + + + + + 登录系统 + + + + + + + + + + \ No newline at end of file -- 2.28.0.windows.1 From a551212813410c7c60011472da998b16380896fb Mon Sep 17 00:00:00 2001 From: XSHA AI Date: Fri, 24 Oct 2025 06:16:02 +0000 Subject: [PATCH 2/2] AI generated changes for conversation 36 --- pom.xml | 5 - .../youfool/course/config/SaTokenConfig.java | 34 ++++ .../course/config/WebSecurityConfig.java | 51 ----- .../course/controller/OAuth2Controller.java | 182 ++++++++++++++++++ .../course/controller/PageController.java | 7 + .../course/controller/TestController.java | 62 ++++++ .../security/OAuth2LoginFailureHandler.java | 31 --- .../security/OAuth2LoginSuccessHandler.java | 96 --------- src/main/resources/application.yml | 25 +-- src/main/resources/templates/login.html | 2 +- 10 files changed, 294 insertions(+), 201 deletions(-) create mode 100644 src/main/java/com/chinaweal/youfool/course/config/SaTokenConfig.java delete mode 100644 src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java create mode 100644 src/main/java/com/chinaweal/youfool/course/controller/OAuth2Controller.java create mode 100644 src/main/java/com/chinaweal/youfool/course/controller/TestController.java delete mode 100644 src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java delete mode 100644 src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java diff --git a/pom.xml b/pom.xml index b7eda40..4f21cef 100644 --- a/pom.xml +++ b/pom.xml @@ -58,11 +58,6 @@ org.springframework.boot spring-boot-starter-thymeleaf - - - org.springframework.boot - spring-boot-starter-oauth2-client - org.springframework.boot diff --git a/src/main/java/com/chinaweal/youfool/course/config/SaTokenConfig.java b/src/main/java/com/chinaweal/youfool/course/config/SaTokenConfig.java new file mode 100644 index 0000000..faf76f9 --- /dev/null +++ b/src/main/java/com/chinaweal/youfool/course/config/SaTokenConfig.java @@ -0,0 +1,34 @@ +package com.chinaweal.youfool.course.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Sa-Token配置 + * + * @author lroyia + * @since 2025/10/24 + **/ +@Configuration +public class SaTokenConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册Sa-Token拦截器 + registry.addInterceptor(new SaInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns( + "/login", + "/error", + "/webjars/**", + "/css/**", + "/js/**", + "/user/auth/**", + "/oauth2/**" + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java b/src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java deleted file mode 100644 index b340e70..0000000 --- a/src/main/java/com/chinaweal/youfool/course/config/WebSecurityConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.chinaweal.youfool.course.config; - -import com.chinaweal.youfool.course.security.OAuth2LoginFailureHandler; -import com.chinaweal.youfool.course.security.OAuth2LoginSuccessHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.web.SecurityFilterChain; - -/** - * Web安全配置 - * - * @author lroyia - * @since 2025/10/24 - **/ -@Configuration -@EnableWebSecurity -public class WebSecurityConfig { - - private final OAuth2LoginSuccessHandler oauth2LoginSuccessHandler; - private final OAuth2LoginFailureHandler oauth2LoginFailureHandler; - - public WebSecurityConfig(OAuth2LoginSuccessHandler oauth2LoginSuccessHandler, - OAuth2LoginFailureHandler oauth2LoginFailureHandler) { - this.oauth2LoginSuccessHandler = oauth2LoginSuccessHandler; - this.oauth2LoginFailureHandler = oauth2LoginFailureHandler; - } - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/login", "/error", "/webjars/**", "/css/**", "/js/**").permitAll() - .requestMatchers("/user/auth/**").permitAll() - .anyRequest().authenticated() - ) - .oauth2Login(oauth2 -> oauth2 - .loginPage("/login") - .successHandler(oauth2LoginSuccessHandler) - .failureHandler(oauth2LoginFailureHandler) - ) - .csrf(csrf -> csrf.disable()) - .formLogin(form -> form - .loginPage("/login") - .permitAll() - ); - - return http.build(); - } -} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/controller/OAuth2Controller.java b/src/main/java/com/chinaweal/youfool/course/controller/OAuth2Controller.java new file mode 100644 index 0000000..c763dec --- /dev/null +++ b/src/main/java/com/chinaweal/youfool/course/controller/OAuth2Controller.java @@ -0,0 +1,182 @@ +package com.chinaweal.youfool.course.controller; + +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.StpUtil; +import com.chinaweal.youfool.framework.springboot.user.entity.UserBase; +import com.chinaweal.youfool.course.common.constants.SessionConstants; +import com.chinaweal.youfool.course.entity.SysUser; +import com.chinaweal.youfool.course.service.SysUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.view.RedirectView; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * OAuth2控制器 + * + * @author lroyia + * @since 2025/10/24 + **/ +@Slf4j +@Controller +@RequestMapping("/oauth2") +public class OAuth2Controller { + + @Value("${gitea.client-id}") + private String clientId; + + @Value("${gitea.client-secret}") + private String clientSecret; + + @Value("${gitea.auth-url}") + private String authUrl; + + @Value("${gitea.token-url}") + private String tokenUrl; + + @Value("${gitea.user-url}") + private String userUrl; + + @Value("${gitea.redirect-uri}") + private String redirectUri; + + @Autowired + private SysUserService sysUserService; + + /** + * 跳转到Gitea授权页面 + * + * @return 重定向到Gitea授权页面 + */ + @GetMapping("/gitea/authorize") + public RedirectView authorize() { + String state = String.valueOf(System.currentTimeMillis()); + String url = String.format("%s?client_id=%s&redirect_uri=%s&response_type=code&state=%s&scope=read:user", + authUrl, clientId, redirectUri, state); + return new RedirectView(url); + } + + /** + * Gitea回调处理 + * + * @param code 授权码 + * @param state 状态 + * @return 重定向到首页 + */ + @GetMapping("/gitea/callback") + public RedirectView callback(@RequestParam String code, @RequestParam String state) { + try { + // 获取访问令牌 + Map tokenParams = new HashMap<>(); + tokenParams.put("client_id", clientId); + tokenParams.put("client_secret", clientSecret); + tokenParams.put("code", code); + tokenParams.put("grant_type", "authorization_code"); + tokenParams.put("redirect_uri", redirectUri); + + RestTemplate restTemplate = new RestTemplate(); + Map tokenResponse = restTemplate.postForObject(tokenUrl, tokenParams, Map.class); + + if (tokenResponse == null || !tokenResponse.containsKey("access_token")) { + throw new RuntimeException("获取access_token失败"); + } + + String accessToken = (String) tokenResponse.get("access_token"); + + // 获取用户信息 + Map userInfo = getUserInfo(accessToken); + + // 处理用户登录 + handleUserLogin(userInfo); + + return new RedirectView("/course/"); + + } catch (Exception e) { + log.error("Gitea OAuth2回调处理失败", e); + return new RedirectView("/course/login?error=true&message=" + e.getMessage()); + } + } + + /** + * 获取用户信息 + * + * @param accessToken 访问令牌 + * @return 用户信息 + */ + private Map getUserInfo(String accessToken) { + RestTemplate restTemplate = new RestTemplate(); + Map headers = new HashMap<>(); + headers.put("Authorization", "token " + accessToken); + + org.springframework.http.HttpEntity entity = new org.springframework.http.HttpEntity<>(headers); + Map userInfo = restTemplate.exchange(userUrl, + org.springframework.http.HttpMethod.GET, entity, Map.class).getBody(); + + return userInfo; + } + + /** + * 处理用户登录 + * + * @param userInfo 用户信息 + */ + private void handleUserLogin(Map userInfo) { + String username = (String) userInfo.get("username"); + String email = (String) userInfo.get("email"); + String avatarUrl = (String) userInfo.get("avatar_url"); + Integer id = (Integer) userInfo.get("id"); + + log.info("Gitea用户登录成功: username={}, email={}, id={}", username, email, id); + + // 生成Gitea Open ID + String giteaOpenId = String.valueOf(id); + + // 检查用户是否已存在 + SysUser sysUser = sysUserService.getUserByGiteaOpenId(giteaOpenId); + + // 如果用户不存在,则创建新用户 + if (sysUser == null) { + sysUser = new SysUser(); + sysUser.setUsername(username); + sysUser.setNickname(username); + sysUser.setEmail(email); + sysUser.setAvatar(avatarUrl); + sysUser.setGiteaOpenId(giteaOpenId); + sysUserService.save(sysUser); + } else { + // 更新用户信息 + sysUser.setEmail(email); + sysUser.setAvatar(avatarUrl); + sysUserService.updateById(sysUser); + } + + // 使用Sa-Token登录 + StpUtil.login(sysUser.getUserId()); + SaSession session = StpUtil.getSession(); + + // 构建用户信息 + UserBase userBase = new UserBase(); + userBase.setUsername(sysUser.getUsername()); + userBase.setEmail(sysUser.getEmail()); + userBase.setAvatarUrl(sysUser.getAvatar()); + userBase.setId(sysUser.getUserId()); + + // 设置权限 + Set permissionSet = new HashSet<>(); + permissionSet.add("user"); + userBase.setPermission(permissionSet); + + // 将用户信息存储至session + session.set(SessionConstants.USER_KEY, userBase); + } +} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/controller/PageController.java b/src/main/java/com/chinaweal/youfool/course/controller/PageController.java index 27e79f5..ac44ee7 100644 --- a/src/main/java/com/chinaweal/youfool/course/controller/PageController.java +++ b/src/main/java/com/chinaweal/youfool/course/controller/PageController.java @@ -1,5 +1,6 @@ package com.chinaweal.youfool.course.controller; +import cn.dev33.satoken.stp.StpUtil; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -29,6 +30,9 @@ public class PageController { */ @GetMapping("/") public String index() { + if (!StpUtil.isLogin()) { + return "redirect:/course/login"; + } return "index"; } @@ -39,6 +43,9 @@ public class PageController { */ @GetMapping("/index") public String indexPage() { + if (!StpUtil.isLogin()) { + return "redirect:/course/login"; + } return "index"; } } \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/controller/TestController.java b/src/main/java/com/chinaweal/youfool/course/controller/TestController.java new file mode 100644 index 0000000..56faf97 --- /dev/null +++ b/src/main/java/com/chinaweal/youfool/course/controller/TestController.java @@ -0,0 +1,62 @@ +package com.chinaweal.youfool.course.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.chinaweal.youfool.framework.springboot.rest.RestResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 测试控制器 + * + * @author lroyia + * @since 2025/10/24 + **/ +@Slf4j +@RestController +@RequestMapping("/test") +public class TestController { + + /** + * 测试接口,需要登录 + * + * @return 测试结果 + */ + @GetMapping("/hello") + public RestResult hello() { + return RestResult.ok("Hello, " + StpUtil.getLoginIdAsString() + "!"); + } + + /** + * 测试管理员权限 + * + * @return 测试结果 + */ + @GetMapping("/admin") + public RestResult admin() { + StpUtil.checkPermission("admin"); + return RestResult.ok("Admin access granted!"); + } + + /** + * 测试用户权限 + * + * @return 测试结果 + */ + @GetMapping("/user") + public RestResult user() { + StpUtil.checkPermission("user"); + return RestResult.ok("User access granted!"); + } + + /** + * 获取当前登录信息 + * + * @return 登录信息 + */ + @GetMapping("/info") + public RestResult info() { + return RestResult.ok("Current login id: " + StpUtil.getLoginIdAsString()); + } +} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java b/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java deleted file mode 100644 index 1bd727d..0000000 --- a/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginFailureHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.chinaweal.youfool.course.security; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * OAuth2登录失败处理器 - * - * @author lroyia - * @since 2025/10/24 - **/ -@Slf4j -@Component -public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { - - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException, ServletException { - log.error("OAuth2登录失败: {}", exception.getMessage()); - - // 重定向到登录页面,带上错误信息 - response.sendRedirect("/course/login?error=true&message=" + java.net.URLEncoder.encode(exception.getMessage(), "UTF-8")); - } -} \ No newline at end of file diff --git a/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java b/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java deleted file mode 100644 index 2030e3c..0000000 --- a/src/main/java/com/chinaweal/youfool/course/security/OAuth2LoginSuccessHandler.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.chinaweal.youfool.course.security; - -import cn.dev33.satoken.session.SaSession; -import cn.dev33.satoken.stp.StpUtil; -import com.chinaweal.youfool.framework.springboot.user.entity.UserBase; -import com.chinaweal.youfool.course.common.constants.SessionConstants; -import com.chinaweal.youfool.course.entity.SysUser; -import com.chinaweal.youfool.course.service.SysUserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * OAuth2登录成功处理器 - * - * @author lroyia - * @since 2025/10/24 - **/ -@Slf4j -@Component -public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { - - @Autowired - private SysUserService sysUserService; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal(); - Map attributes = oauth2User.getAttributes(); - - // 获取Gitea用户信息 - String username = (String) attributes.get("username"); - String email = (String) attributes.get("email"); - String avatarUrl = (String) attributes.get("avatar_url"); - Integer id = (Integer) attributes.get("id"); - - log.info("Gitea用户登录成功: username={}, email={}, id={}", username, email, id); - - // 生成Gitea Open ID - String giteaOpenId = String.valueOf(id); - - // 检查用户是否已存在 - SysUser sysUser = sysUserService.getUserByGiteaOpenId(giteaOpenId); - - // 如果用户不存在,则创建新用户 - if (sysUser == null) { - sysUser = new SysUser(); - sysUser.setUsername(username); - sysUser.setNickname(username); - sysUser.setEmail(email); - sysUser.setAvatar(avatarUrl); - sysUser.setGiteaOpenId(giteaOpenId); - sysUserService.save(sysUser); - } else { - // 更新用户信息 - sysUser.setEmail(email); - sysUser.setAvatar(avatarUrl); - sysUserService.updateById(sysUser); - } - - // 使用Sa-Token登录 - StpUtil.login(sysUser.getUserId()); - SaSession session = StpUtil.getSession(); - - // 构建用户信息 - UserBase userBase = new UserBase(); - userBase.setUsername(sysUser.getUsername()); - userBase.setEmail(sysUser.getEmail()); - userBase.setAvatarUrl(sysUser.getAvatar()); - userBase.setId(sysUser.getUserId()); - - // 设置权限 - Set permissionSet = new HashSet<>(); - permissionSet.add("user"); - userBase.setPermission(permissionSet); - - // 将用户信息存储至session - session.set(SessionConstants.USER_KEY, userBase); - - // 重定向到首页 - response.sendRedirect("/course/"); - } -} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 47bb0de..024daf9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,23 +14,14 @@ spring: suffix: .html encoding: UTF-8 mode: HTML - # OAuth2配置 - security: - oauth2: - client: - registration: - gitea: - client-id: ${GITEA_CLIENT_ID:your-gitea-client-id} - client-secret: ${GITEA_CLIENT_SECRET:your-gitea-client-secret} - authorization-grant-type: authorization_code - redirect-uri: ${BASE_URL:http://localhost:8080}/course/login/oauth2/code/gitea - scope: read:user - provider: - gitea: - authorization-uri: ${GITEA_AUTH_URL:https://gitea.com/login/oauth/authorize} - token-uri: ${GITEA_TOKEN_URL:https://gitea.com/login/oauth/access_token} - user-info-uri: ${GITEA_USER_URL:https://gitea.com/api/v1/user} - user-name-attribute: username + # Gitea OAuth2配置 + gitea: + client-id: ${GITEA_CLIENT_ID:your-gitea-client-id} + client-secret: ${GITEA_CLIENT_SECRET:your-gitea-client-secret} + auth-url: ${GITEA_AUTH_URL:https://gitea.com/login/oauth/authorize} + token-url: ${GITEA_TOKEN_URL:https://gitea.com/login/oauth/access_token} + user-url: ${GITEA_USER_URL:https://gitea.com/api/v1/user} + redirect-uri: ${BASE_URL:http://localhost:8080}/course/oauth2/gitea/callback datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 78ecd8b..43ee642 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -136,7 +136,7 @@ -