generated from youfool-project/youfool-prj-springboot-template
xsha/lroyia/task-20251024-055457 #1
15
pom.xml
15
pom.xml
|
|
@ -53,6 +53,21 @@
|
||||||
<version>1.18.42</version>
|
<version>1.18.42</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Thymeleaf模板引擎 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- OAuth2客户端 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- Web相关依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.rest.ResultCode;
|
||||||
import com.chinaweal.youfool.framework.springboot.user.entity.UserBase;
|
import com.chinaweal.youfool.framework.springboot.user.entity.UserBase;
|
||||||
import com.chinaweal.youfool.course.common.constants.SessionConstants;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
@ -30,6 +33,9 @@ import java.util.Set;
|
||||||
@RequestMapping("/user/auth")
|
@RequestMapping("/user/auth")
|
||||||
public class LoginController extends BaseController {
|
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();
|
SaSession session = StpUtil.getSession();
|
||||||
// 将用户信息存储至session TODO:登录信息存到这里
|
// 将用户信息存储至session
|
||||||
UserBase userBase = new UserBase();
|
UserBase userBase = new UserBase();
|
||||||
|
userBase.setUsername(sysUser.getUsername());
|
||||||
|
userBase.setEmail(sysUser.getEmail());
|
||||||
|
userBase.setAvatar(sysUser.getAvatar());
|
||||||
|
userBase.setId(sysUser.getUserId());
|
||||||
|
|
||||||
Set<String> permissionSet = new HashSet<>();
|
Set<String> permissionSet = new HashSet<>();
|
||||||
permissionSet.add("admin");
|
permissionSet.add("admin");
|
||||||
userBase.setPermission(permissionSet);
|
userBase.setPermission(permissionSet);
|
||||||
session.set("user", userBase);
|
session.set(SessionConstants.USER_KEY, userBase);
|
||||||
|
|
||||||
return RestResult.ok();
|
return RestResult.ok();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,6 +81,15 @@ public class SysUser implements Serializable {
|
||||||
@Schema(description = "邮箱", example = "user@example.com")
|
@Schema(description = "邮箱", example = "user@example.com")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*
|
||||||
|
* <p>用户的登录密码,存储时应该加密处理。</p>
|
||||||
|
*/
|
||||||
|
@TableField("password")
|
||||||
|
@Schema(description = "密码", example = "encrypted_password")
|
||||||
|
private String password;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gitea Open ID
|
* Gitea Open ID
|
||||||
*
|
*
|
||||||
|
|
@ -114,16 +123,18 @@ public class SysUser implements Serializable {
|
||||||
* @param name 姓名
|
* @param name 姓名
|
||||||
* @param avatar 头像
|
* @param avatar 头像
|
||||||
* @param email 邮箱
|
* @param email 邮箱
|
||||||
|
* @param password 密码
|
||||||
* @param giteaOpenId Gitea Open ID
|
* @param giteaOpenId Gitea Open ID
|
||||||
*/
|
*/
|
||||||
public SysUser(String userId, String username, String nickname, String name,
|
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.userId = userId;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.nickname = nickname;
|
this.nickname = nickname;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.avatar = avatar;
|
this.avatar = avatar;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
this.password = password;
|
||||||
this.giteaOpenId = giteaOpenId;
|
this.giteaOpenId = giteaOpenId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +147,7 @@ public class SysUser implements Serializable {
|
||||||
", name='" + name + '\'' +
|
", name='" + name + '\'' +
|
||||||
", avatar='" + avatar + '\'' +
|
", avatar='" + avatar + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
|
", password='" + password + '\'' +
|
||||||
", giteaOpenId='" + giteaOpenId + '\'' +
|
", giteaOpenId='" + giteaOpenId + '\'' +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<String, Object> 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<String> permissionSet = new HashSet<>();
|
||||||
|
permissionSet.add("user");
|
||||||
|
userBase.setPermission(permissionSet);
|
||||||
|
|
||||||
|
// 将用户信息存储至session
|
||||||
|
session.set(SessionConstants.USER_KEY, userBase);
|
||||||
|
|
||||||
|
// 重定向到首页
|
||||||
|
response.sendRedirect("/course/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,4 +22,20 @@ import com.chinaweal.youfool.course.entity.SysUser;
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public interface SysUserService extends IService<SysUser> {
|
public interface SysUserService extends IService<SysUser> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名获取用户信息
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
SysUser getUserByUsername(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据Gitea Open ID获取用户信息
|
||||||
|
*
|
||||||
|
* @param giteaOpenId Gitea Open ID
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
SysUser getUserByGiteaOpenId(String giteaOpenId);
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.chinaweal.youfool.course.service.impl;
|
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.chinaweal.youfool.course.entity.SysUser;
|
import com.chinaweal.youfool.course.entity.SysUser;
|
||||||
import com.chinaweal.youfool.course.mapper.SysUserMapper;
|
import com.chinaweal.youfool.course.mapper.SysUserMapper;
|
||||||
|
|
@ -28,4 +29,18 @@ import org.springframework.stereotype.Service;
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
|
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SysUser getUserByUsername(String username) {
|
||||||
|
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(SysUser::getUsername, username);
|
||||||
|
return getOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SysUser getUserByGiteaOpenId(String giteaOpenId) {
|
||||||
|
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(SysUser::getGiteaOpenId, giteaOpenId);
|
||||||
|
return getOne(queryWrapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,30 @@ spring:
|
||||||
active: dev
|
active: dev
|
||||||
application:
|
application:
|
||||||
name: youfool-lesson
|
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:
|
datasource:
|
||||||
dynamic:
|
dynamic:
|
||||||
primary: master #设置默认的数据源或者数据源组,默认值即为master
|
primary: master #设置默认的数据源或者数据源组,默认值即为master
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>首页</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.logout-btn {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.logout-btn:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
.welcome-card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.welcome-title {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.welcome-text {
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
<h1>课程管理系统</h1>
|
||||||
|
<div class="user-info">
|
||||||
|
<span>欢迎,<span id="username"></span></span>
|
||||||
|
<a href="/course/user/auth/logout" class="logout-btn">退出登录</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="content">
|
||||||
|
<div class="welcome-card">
|
||||||
|
<h2 class="welcome-title">欢迎来到课程管理系统</h2>
|
||||||
|
<p class="welcome-text">
|
||||||
|
这里是一个基于Spring Boot + Thymeleaf + Gitea OAuth2的课程管理系统。
|
||||||
|
您可以使用用户名密码登录,也可以使用Gitea账号进行第三方登录。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// 获取用户信息
|
||||||
|
async function loadUserInfo() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/course/user/auth/login/info');
|
||||||
|
if (response.data.code === 200 && response.data.data) {
|
||||||
|
document.getElementById('username').textContent = response.data.data.username || '用户';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户信息失败:', error);
|
||||||
|
// 如果未登录,跳转到登录页面
|
||||||
|
window.location.href = '/course/login';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取用户信息
|
||||||
|
window.addEventListener('load', loadUserInfo);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>登录系统</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.login-container {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
padding: 40px;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
.login-title {
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.login-btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
position: relative;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.divider::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
.divider span {
|
||||||
|
background-color: white;
|
||||||
|
padding: 0 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.gitea-login-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #609926;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.gitea-login-btn:hover {
|
||||||
|
background-color: #4d7a1f;
|
||||||
|
}
|
||||||
|
.gitea-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
color: #dc3545;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #dc3545;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<h1 class="login-title">登录系统</h1>
|
||||||
|
|
||||||
|
<div class="error-message" id="errorMessage" th:if="${param.error}" th:text="${param.message}"></div>
|
||||||
|
|
||||||
|
<form id="loginForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input type="text" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">密码</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="login-btn">登录</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="divider">
|
||||||
|
<span>或</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/oauth2/authorization/gitea" class="gitea-login-btn">
|
||||||
|
<svg class="gitea-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.686 1.21 2.768 1.766 2.141 5.51 2.092 5.51 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.112-.012-.263-.02-.42-.02zm.531 1.268c.163.011.405.027.658.054l.426.05 3.23.294c.407.038.848.072 1.295.104 0 0 .39.023.948.054.558.031 1.243.065 1.243.065.55.027 1.107.038 1.243.046.39.015.604.015.604.015l.007 4.586 2.394 1.135V6.324c.492-.004.983-.015 1.463-.023l.847-.015c1.18-.023 2.344-.07 2.344-.07l.273-.015c.156 0 .294-.004.422-.004.863 0 1.027.169 1.077.218.112.113.135.42.15.508.015.124.046.365.058.697.031 1.336-.218 3.098-.631 4.688-.073.28-.156.562-.248.836-.27.83-.956 2.478-.956 2.478-.14.352-.304.75-.499 1.169-.397.852-.82 1.627-.82 1.627s-.23.374-.615.665a2.525 2.525 0 0 1-.523.267c-.15.046-.306.02-.508.027l-.603.015c-.607.008-1.206.004-1.206.004H11.25c-1.278-.12-2.44-2.152-3.03-3.042-.793-1.224-1.549-3.12-1.549-3.12l-.812-.02c-.407-.008-.844-.02-1.203-.02-1.647 0-3.12-.52-3.87-1.399C.13 9.98-.191 7.767.652 6.564c.472-.677 1.2-1.06 1.845-1.24a4.45 4.45 0 0 1 1.243-.153z"/>
|
||||||
|
</svg>
|
||||||
|
使用 Gitea 登录
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jsencrypt@3.2.1/bin/jsencrypt.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// 获取公钥
|
||||||
|
let publicKey = '';
|
||||||
|
fetch('/course/user/auth/publicKey')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
publicKey = data.data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('获取公钥失败:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 登录表单提交
|
||||||
|
document.getElementById('loginForm').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const username = document.getElementById('username').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 加密密码
|
||||||
|
const encrypt = new JSEncrypt();
|
||||||
|
encrypt.setPublicKey(publicKey);
|
||||||
|
const encryptedPassword = encrypt.encrypt(password);
|
||||||
|
|
||||||
|
if (!encryptedPassword) {
|
||||||
|
throw new Error('密码加密失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送登录请求
|
||||||
|
const response = await axios.post('/course/user/auth/login', {
|
||||||
|
username: username,
|
||||||
|
password: encryptedPassword,
|
||||||
|
encrypt: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.code === 200) {
|
||||||
|
// 登录成功,跳转到首页
|
||||||
|
window.location.href = '/';
|
||||||
|
} else {
|
||||||
|
errorMessage.textContent = response.data.message || '登录失败';
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('登录请求失败:', error);
|
||||||
|
errorMessage.textContent = '登录请求失败,请稍后重试';
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue