Login + getInfo + Register 后端 API 书写, login + logout 前端书写

This commit is contained in:
2023-02-20 22:15:52 +08:00
parent 78db9a28c8
commit 0badc0f83d
23 changed files with 802 additions and 8 deletions
+30
View File
@@ -68,6 +68,30 @@
<version>3.0.2</version>
</dependency>
<!-- 用于使用 JWT 验证-->
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -83,6 +107,12 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@@ -1,17 +1,55 @@
package com.kob.backend.config;
/*
主要作用:放行登录、注册等接口
*/
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
//明文加密
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
// TODO: 2023/2/19 WebSecurityConfigurerAdapter 已被弃用,注意替换
@Configuration
@EnableWebSecurity
public class SecurityConfig {
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
// 配置密码加密方式
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/*
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
用于配置公开链接
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
@@ -0,0 +1,68 @@
package com.kob.backend.config.filter;
//filter 类用于验证
/*
主要作用:
1. 用来验证jwt token,如果验证成功,则将User信息注入上下文中
*/
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import com.sun.xml.internal.bind.v2.TODO;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
// TODO 这里可以更改 bearar 为其他字符作为验证前缀
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用户名未登录");
}
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
@@ -12,7 +12,8 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
//实现对用户类 User 的增删查操作
//实现对用户类 User 的增删查操作->这个类仅用于调试,故 Deparecated 掉
@Deprecated
@RestController
public class UserController {
@@ -58,7 +59,7 @@ public class UserController {
// 加密明文密码并存入密文到数据库
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode(password);
User user = new User(userId, username, encodedPassword);
User user = new User(userId, username, encodedPassword,"");
userMapper.insert(user);
return "Add User Successfully";
}
@@ -0,0 +1,20 @@
package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.InfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class InfoController {
@Autowired
private InfoService infoService;
// 获取信息
@GetMapping("/user/account/info/")
public Map<String,String> getInfo(){
return infoService.getInfo();
}
}
@@ -0,0 +1,27 @@
package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
//@RestController 配合 @PostMapping("/.../.../") 使用:映射 Url 地址
@RestController
public class LoginController {
// @Autowired 注入接口
@Autowired
private LoginService loginService;
// 登录使用 POST 请求,密文传输,更安全
@PostMapping("/user/account/token/")
public Map<String,String> getToken(@RequestParam Map<String,String> map){
String username = map.get("username");
String password = map.get("password");
return loginService.getToken(username,password);
}
}
@@ -0,0 +1,24 @@
package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class RegisterController {
@Autowired
private RegisterService registerService;
@PostMapping("/user/account/register/")
public Map<String, String> register(@RequestParam Map<String, String> map) {
String username = map.get("username");
String password = map.get("password");
String confirmedPassword = map.get("confirmedPassword");
return registerService.register(username, password, confirmedPassword);
}
}
@@ -6,6 +6,8 @@ Data 用于编译时自动生成 getter setter 方法;
NoArgsConstructor 用于生成无参构造函数;
AllArgsConstructor 用于生成所有参数构造函数.
*/
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -15,9 +17,12 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class User {
// 使用对象类型定义而不是 Int 防止 Mybatis 报错
// 使用对象类型定义而不是 Int 防止 Mybatis 报错
// @TableId(type = IdType.AUTO) 用于 id 的自增
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String photo;
}
@@ -18,6 +18,12 @@ public class UserDetailsServiceImpl implements UserDetailsService {
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*
从数据库里读取用户信息,比对用户名密码与数据库存在的用户名密码,信息一致,给用户发一个 sessionID ,
存到用户本地浏览器,服务器同时会自己存一份。未来用户再登录时服务器会将自己的 sessionID 与用户发过来
请求包含的 sessionID 进行比照,如果一致,则授权成功。
*/
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
User user = userMapper.selectOne(queryWrapper);
@@ -0,0 +1,34 @@
package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.InfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class InfoServiceImpl implements InfoService {
@Override
public Map<String, String> getInfo() {
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
User user = loginUser.getUser();
Map<String,String> map = new HashMap<>();
map.put("error_message","success");
map.put("id",user.getId().toString());
map.put("username",user.getUsername());
map.put("photo",user.getPhoto());
return map;
}
}
@@ -0,0 +1,50 @@
package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.LoginService;
import com.kob.backend.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
//@Service 注解用于注入接口实现到 Spring 里面
@Service
public class LoginServiceImpl implements LoginService {
// @Autowired 用于注入
@Autowired
private AuthenticationManager authenticationManager;
@Override
public Map<String, String> getToken(String username, String password) {
// 封装用户名和密码成 UsernamePasswordAuthenticationToken 类:此类不存储明文,而是存储加密之后的字符串
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
// 验证是否正常登录,登录验证失败时会自动处理(报异常)
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 登录成功,取出用户
UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
User user = loginUser.getUser();
// 封装 jwt 信息:将 userID 转换为 String
String jwt = JwtUtil.createJWT(user.getId().toString());
/*
成功之后返回结果:
1. "error_message" 为 success;失败之后会报异常,自动处理掉
2. "token" 返回 jwt-token 信息
*/
Map<String,String> map = new HashMap<>();
map.put("error_message","success");
map.put("token",jwt);
return map;
}
}
@@ -0,0 +1,81 @@
package com.kob.backend.service.impl.user.account;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.pojo.User;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
@Service
public class RegisterServiceImpl implements RegisterService {
// 这里使用 @Autowired 注入是为了调用数据库,然后进行数据库查询,比较是否有用户名重复
@Autowired
private UserMapper userMapper;
@Override
public Map<String, String> register(String username, String password, String confirmedPassword) {
Map<String, String> map = new HashMap<>();
// 如果用户名为空,则提示用户返回
if (username == null) {
map.put("error_message", "用户名不能为空");
return map;
}
if (password == null || confirmedPassword == null) {
map.put("error_message", "密码不能为空");
return map;
}
// 用户名需要将输入的首尾空格删掉
username = username.trim();
if (username.length() == 0) {
map.put("error_message", "用户名不能为空");
return map;
}
if (password.length() == 0) {
map.put("error_message", "密码不能为空");
return map;
}
if (username.length() > 100) {
map.put("error_message", "用户名过长");
return map;
}
if (password.length() > 100 || confirmedPassword.length() > 100) {
map.put("error_message", "密码过长");
return map;
}
// 密码验证是 String 类型比较,应该用 equals() 方法
if (!confirmedPassword.equals(password)) {
map.put("error_message", "两次密码输入不一致");
return map;
}
/*
查询数据库里是否有用户名 this.username 已存在的用户,并将结果存入 users 中,
如果发现 users 不是空的,则告诉注册用户当前用户名已存在
*/
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.eq("username", username);
List<User> users = userMapper.selectList(queryWrapper);
if (!users.isEmpty()) {
map.put("error_message", "用户名已存在");
return map;
}
// 异常情况判断结束,开始将合法用户注册信息注入数据库
// 对密码进行加密
String encodedPassword = new BCryptPasswordEncoder().encode(password);
// 默认头像
String photo = "https://cdn.acwing.com/media/user/profile/photo/253652_lg_e3d8435b66.jpg";
// id 是数据库自增,这里生成新用户只需要将 id 参数写为 null 即可
User user = new User(null,username,encodedPassword,photo);
userMapper.insert(user);
map.put("error_message","成功注册");
return map;
}
}
@@ -0,0 +1,8 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//根据令牌返回用户信息
public interface InfoService {
public Map<String,String> getInfo();
}
@@ -0,0 +1,8 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//验证用户名密码,验证成功后返回jwt token(令牌)
public interface LoginService {
public Map<String,String> getToken(String username, String password);
}
@@ -0,0 +1,9 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//注册账号
public interface RegisterService {
// confirmedPassword 密码确认
public Map<String,String> register(String username, String password, String confirmedPassword);
}
@@ -0,0 +1,74 @@
package com.kob.backend.utils;
//jwt工具类,用来创建、解析 jwt token
/*
主要作用:
1. 将字符串加上密钥,加上有效期,转换为加密后的字符串;
2. 给一个令牌,将其 userID 解析出来.
依赖:添加到 pom.xml 中,然后使用 Maven 重新加载依赖项
jjwt-apt;
jjwt-impl;
jjwt-jackson.
*/
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
@Component
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 有效期14天:单位-毫秒 ms
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232113afasdfad"; // 密钥:随机字符串-大小写英文字母+数字
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody();
}
}