SpringBoot 实现密码加密以及登录成功token实现
谨以此文章记录自己的学习过程,借以帮助有同样需求的小伙伴,实现的不完善,只是将大概的主要内容实现而已~
一、demo所需的技术
springBoot、springSecurity、mysql、lombok
部分依赖pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<!-- 生成token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
二、项目结构
三、实现过程
3.1数据库结构
application.yml
这里配置mysql的驱动 以及设置端口号
spring:
datasource:
url: jdbc:mysql://localhost:3306/bishi?my_db_01=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
server:
port: 8183
DBUtil.class
这里将mysql的一些操作直接封装成一个实体类
package com.example.bcryptpasswordencoder.utils;
import java.sql.*;
public class DBUtil {
static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/my_db_01?useUnicode=true&characterEncoding=UTF-8";
static final String USER = "root";
static final String PASS = "root";
static {
try {
Class.forName(JDBC_DRIVER);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Connection conn;
private Statement stat;
private PreparedStatement pstmt;
public void openConnection() {
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);
conn.setAutoCommit(true);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Statement getStatement() {
openConnection();
try {
stat = conn.createStatement();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stat;
}
public PreparedStatement getPreparedStatement(String sql) {
openConnection();
try {
pstmt = conn.prepareStatement(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return pstmt;
}
public void closeResource() {
try {
if (pstmt != null && !pstmt.isClosed()) {
pstmt.close();
}
if (stat != null && !stat.isClosed()) {
stat.close();
}
if (conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
3.2 实体类 Users
这里习惯注意和数据库的字段名匹配比较好,以后用到mybatis这样的操作可以避免一些错误
@Data 的使用自动生成set和get方法
package com.example.bcryptpasswordencoder.entity;
import lombok.Data;
@Data
public class Users {
private long id;
private String username;
private String password;
private String usernike;
}
3.3 Dao层
该项目主要实现三个接口,添加用户、登录、和更新操作
package com.example.bcryptpasswordencoder.dao;
import java.util.Map;
public interface UsersDao {
// 添加用户 对密码进行加密
Map<String,Object> bcryptPassword(String username,String password);
// 登录
Map<String,Object> login(String username,String password);
// 更新昵称
Map<String,Object> updateUserNick(String username,String usernick);
}
3.4 controller层
注册用户模块:主要是为了实现用户注册成功后,对存入数据库的密码进行加密(但是这里我没有实现对密码用户名的限定等等,你们自己可以deng
登录模块:这里主要是实现用户登录成功之后使用jwt生成token
更新模块:主要是为了实现使用token,对token的有效判断还有token解密,最后实现用户的更新操作
package com.example.bcryptpasswordencoder.controller;
import com.example.bcryptpasswordencoder.impl.UsersImpl;
import com.example.bcryptpasswordencoder.utils.JwtUtil;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RequestMapping("/users")
@RestController
@CrossOrigin
public class UsersController {
UsersImpl users = new UsersImpl();
//注册用户
@PostMapping(value = "/bcryptPassword")
public Map<String, Object> bcryptPassword(String username, String password) {
Map<String,Object> map = new HashMap<>();
map = users.bcryptPassword(username,password);
return map;
}
//登录
@PostMapping(value = "/login")
public Map<String, Object> login(String username, String password){
Map<String,Object> map = new HashMap<>();
map = users.login(username,password);
return map;
}
//更新
@PostMapping(value = "/updateUserNick")
public Map<String,Object> updateUserNick(HttpServletRequest httpServletRequest, String userNick){
// System.out.println(userNick);
Map<String,Object> map = new HashMap<>();
// 首选判断token是否有效
if(!JwtUtil.checkToken(httpServletRequest)){
map.put("code",500);
map.put("msg","token无效");
return map;
}
// 解密username
String username = JwtUtil.getUserIdByJwtToken(httpServletRequest);
map = users.updateUserNick(username,userNick);
return map;
}
}
3.5 impl
加密使用的是 BCryptPasswordEncoder
我这里是为了顺便练习一下jdbc,大家觉得繁琐可以直接使用mybatis.
package com.example.bcryptpasswordencoder.impl;
import com.example.bcryptpasswordencoder.dao.UsersDao;
import com.example.bcryptpasswordencoder.entity.Users;
import com.example.bcryptpasswordencoder.utils.DBUtil;
import com.example.bcryptpasswordencoder.utils.JwtUtil;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class UsersImpl implements UsersDao {
DBUtil dbUtil = new DBUtil();
// 创建一个BCryptPasswordEncoder对象
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
public Map<String, Object> bcryptPassword(String username, String password) {
// 对密码进行加密
String password1 = passwordEncoder.encode(password);
Map<String,Object> map = new HashMap<>();
// 数据库操作
dbUtil.openConnection();
PreparedStatement ps;
// 定义一个sql语句
String sql="insert into users(username,password) values(?,?)";
ps = dbUtil.getPreparedStatement(sql);
int count=0;//判断sqlzhi
try {
ps.setString(1,username);
ps.setString(2,password1);
count = ps.executeUpdate();
// 关闭数据库连接
dbUtil.closeResource();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
if(count>0) {
map.put("code", 200);
map.put("count", 0);
map.put("msg", "添加用户成功");
map.put("data", "");
}else{
map.put("code", 200);
map.put("count", 0);
map.put("msg", "添加用户失败");
}
return map;
}
@Override
public Map<String, Object> login(String username, String password) {
Map<String,Object> map = new HashMap<>();
ArrayList<Users> users = new ArrayList<>();
// 通过username查找用户
// 定义sql语句
String sql ="select * from users where username=?";
// 数据库操作
dbUtil.openConnection();
PreparedStatement ps;
ps = dbUtil.getPreparedStatement(sql);
try {
ps.setString(1,username);
ResultSet rs = ps.executeQuery();
Users user;
while(rs.next())
{
user = new Users();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
users.add(user);
}
dbUtil.closeResource();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
if(users.size()>0){
// 证明有该用户
// 判断密码是否正确
boolean matches = passwordEncoder.matches(password,users.get(0).getPassword());
// 生成token
String token = JwtUtil.getJwtToken(users.get(0).getId(),users.get(0).getUsername());
if(matches){
// 匹配成功
map.put("code", 200);
map.put("count", users.size());
map.put("msg", "登录成功");
map.put("data", users);
map.put("token",token);
}else{
map.put("code", 500);
map.put("count", 0);
map.put("msg", "登录失败");
}
}
return map;
}
@Override
public Map<String, Object> updateUserNick(String username, String userNick) {
Map<String,Object> map = new HashMap<>();
int i= 0;//判断更新是否成功
// 数据库操作
dbUtil.openConnection();
PreparedStatement ps;
try {
// 对昵称进行修改
// 定义sql语句
String strSql = "update users set nickname = ? where username=?";
ps = dbUtil.getPreparedStatement(strSql);
ps.setString(1,userNick);
ps.setString(2,username);
i= ps.executeUpdate();
dbUtil.closeResource();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
if(i>0){
map.put("code", 200);
map.put("count", 0);
map.put("msg", "更新成功");
map.put("data", "");
}else{
map.put("code", 500);
map.put("count", 0);
map.put("msg", "更新失败");
}
return map;
}
}
3.6JwtUtil
这里大家如果不理解的话可以查阅这篇文章,写得很好https://blog.csdn.net/weixin_45070175/article/details/118559272
package com.example.bcryptpasswordencoder.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
public class JwtUtil {
// token 时效 12小时
public static final long EXPIRE = 1000*60*60*12;
// 签名哈希的密钥,对于不同的加密算法来说含义不同
public static final String APP_SECRET = "hss200923usersToken";
/**
* 根据用户id和用户名生成token
* @param id 用户id
* @param username 用户名称
* @return JWT规则生成的token
*/
public static String getJwtToken(long id,String username){
String JwtToken = Jwts.builder()
.setHeaderParam("typ","JWT")
.setHeaderParam("alg","HS256")
.setSubject("users")
.setIssuedAt(new Date())//token 保留时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//token失效时间
.claim("id",id)
.claim("username",username)
// HS256算法实际上就是MD5加盐值,此时APP_SECRET就代表盐值
.signWith(SignatureAlgorithm.HS256,APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @param jwtToken token字符串
* @return 如果token 有效返回true,否则返回false
*/
public static boolean checkToken(String jwtToken){
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
}catch(Exception e){
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token 是否存在与有效
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request){
try{
// 从http请求头中获取token字符串
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
}catch(Exception e){
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token 获取用户username
* @param request Http 请求对象
* @return 解析token后获得的用户id
*/
public static String getUserIdByJwtToken(HttpServletRequest request){
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("username");
}
}