若依实现微信小程序登录&APP登录
- 前言
- 搭建若依项目
- APP登录
- 1.数据库创建新的用户表
- 2.创建AppUser实体类
- 3.创建 AppUserMapper.xml 映射文件
- 4.创建 AppUserMapper 数据层
- 5.创建 IAppUserService 业务层
- 6.创建 AppUserServiceImpl 业务实现层
- 7.创建 LoginAppUser 登录用户身份权限类
- 8.创建 LoginAppBody 用户登录对象类
- 9.添加通用常量信息 Constants & CacheConstants 常量信息
- 10.创建 AppTokenService TOKEN处理类
- 11.创建 AppLoginService 登录校验方法类
- 12.修改JwtAuthenticationTokenFilter token过滤器 验证token有效性
- 13.安全服务工具类 SecurityUtils 添加信息
- 14.web层通用数据类 BaseController 添加信息
- 15.创建 AppLoginController 控制器
- 16.SecurityConfig添加匿名访问地址
- 17.测试接口
- 小程序登录
- 谢谢您的耐心观看,点赞 收藏 转发 是对我最大的支持
前言
近期公司有项目要做微信小程序和app的项目,为了快速开发采用若依框架,本文使用的若依框架版本为3.8.8,当前时间为2024年8月是若依最新版本。
在原有的后台管理系统登录流程上,新增新的登录接口用于小程序或APP登录,创建新的用户表,新的token认证登,与原有的后台管理系统登录流程分开,流程更清晰,逻辑更严谨。
本文需要对SpringSecurity+jwt的认证流程有一定的了解,如果不了解单纯复制粘贴也可以实现,但是你会看的很难受,建议还是学一学。
使用lombok快速生成get,set方法
在我的项目中修改了项目名,ruoyi改成了app,大家对照目录的时候注意
搭建若依项目
参照官方文档搭建即可 若依分离版官方文档
APP登录
1.数据库创建新的用户表
创建表
CREATE TABLE `app_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`mobile` varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`salt` varchar(50) DEFAULT '' COMMENT '盐',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
插入测试数据
insert into `app_user`(`user_id`,`user_name`,`nick_name`,`email`,`mobile`,`sex`,`avatar`,`password`,`salt`,`status`,`del_flag`,`login_ip`,`login_date`,`create_by`,`create_time`,`update_by`,`update_time`,`remark`) values (1,'xing','星哥','123456@163.com','19999999999','1','','bb6d30fe528388b8f08631afb5a22c5c','GoEskKGp','0','0','192.168.3.4','2024-08-13 09:48:46','管理员','2024-08-13 09:24:06','用户注册创建','2024-08-13 09:24:06','测试app账号');
2.创建AppUser实体类
此实体类为上述数据库表AppUser用户表实体类
注意:与 SysUser 同级目录
创建目录:ruoyi-common——>src——>main——>java——>com.ruoyi.common——>core——>domian——>entity
package com.app.common.core.domain.entity;
import com.app.common.core.domain.BaseEntity;
import lombok.Data;
import java.util.Date;
/**
* 用户对象 app_user
*/
@Data
public class AppUser extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String mobile;
/**
* 用户性别(0=男,1=女,2=未知)
*/
private String sex;
/**
* 用户头像
*/
private String avatar;
/**
* 密码
*/
private String password;
/**
* 盐
*/
private String salt;
/**
* 帐号状态(0=正常,1=停用)
*/
private String status;
/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private Date loginDate;
}
3.创建 AppUserMapper.xml 映射文件
目录:ruoyi-system——>src——>main——>resources——>mapper.system
注意:与 SysUserMapper.xml 同级目录
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.app.system.mapper.AppUserMapper">
<resultMap type="AppUser" id="AppUserResult">
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="nickName" column="nick_name"/>
<result property="email" column="email"/>
<result property="mobile" column="mobile"/>
<result property="sex" column="sex"/>
<result property="avatar" column="avatar"/>
<result property="password" column="password"/>
<result property="salt" column="salt"/>
<result property="status" column="status"/>
<result property="delFlag" column="del_flag"/>
<result property="loginIp" column="login_ip"/>
<result property="loginDate" column="login_date"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectAppUserVo">
select user_id, user_name, nick_name, email, avatar, mobile, password, salt, sex, status, del_flag, login_ip, login_date, create_by, create_time, remark from app_user
</sql>
<select id="selectAppUserByUserName" parameterType="String" resultMap="AppUserResult">
<include refid="selectAppUserVo"/>
where user_name = #{userName} and del_flag = '0'
</select>
<update id="updateAppUser" parameterType="AppUser">
update app_user
<set>
<if test="userName != null and userName != ''">user_name = #{userName},</if>
<if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
<if test="email != null ">email = #{email},</if>
<if test="mobile != null ">mobile = #{mobile},</if>
<if test="sex != null and sex != ''">sex = #{sex},</if>
<if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
<if test="password != null and password != ''">password = #{password},</if>
<if test="salt != null and salt != ''">salt = #{salt},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if>
<if test="loginDate != null">login_date = #{loginDate},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if>
update_time = sysdate()
</set>
where user_id = #{userId}
</update>
</mapper>
4.创建 AppUserMapper 数据层
目录:ruoyi-system——>src——>main——>java——>com.ruoyi.system——>mapper
注意:与 SysUserMapper 同级目录
package com.app.system.mapper;
import com.app.common.core.domain.entity.AppUser;
/**
* 用户表 数据层
*
* @author ruoyi
*/
public interface AppUserMapper {
/**
* 通过用户名查询用户
*
* @param userName 用户名
* @return 用户对象信息
*/
AppUser selectAppUserByUserName(String userName);
/**
* 修改用户信息
*
* @param appUser 用户信息
* @return 结果
*/
int updateAppUser(AppUser appUser);
}
5.创建 IAppUserService 业务层
目录:ruoyi-system——>src——>main——>java——>com.ruoyi.system——>service
注意:与 ISysUserService 同级目录
package com.app.system.service;
import com.app.common.core.domain.entity.AppUser;
/**
* 用户 业务层
*
* @author ruoyi
*/
public interface IAppUserService {
/**
* 通过用户账号查询用户
*
* @param userName 用户账号
* @return 用户对象信息
*/
AppUser selectAppUserByUserName(String userName);
/**
* 修改用户信息
*
* @param appUser 用户信息
* @return 结果
*/
int updateAppUser(AppUser appUser);
/**
* 密码校验
*
* @param password 明文
* @param salt 盐
* @param hashPwd 密文
* @return boolean
*/
boolean checkPassword(String password, String salt, String hashPwd);
}
6.创建 AppUserServiceImpl 业务实现层
目录:ruoyi-system——>src——>main——>java——>com.ruoyi.system——>service——>impl
注意:与 SysUserServiceImpl 同级目录
package com.app.system.service.impl;
import com.app.common.core.domain.entity.AppUser;
import com.app.system.mapper.AppUserMapper;
import com.app.system.service.IAppUserService;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
/**
* 用户 业务层处理
*
* @author ruoyi
*/
@Service
public class AppUserServiceImpl implements IAppUserService {
@Resource
private AppUserMapper appUserMapper;
/**
* 通过用户名查询用户
*
* @param userName 用户名
* @return 用户对象信息
*/
@Override
public AppUser selectAppUserByUserName(String userName) {
return appUserMapper.selectAppUserByUserName(userName);
}
/**
* 修改保存用户信息
*
* @param appUser 用户信息
* @return 结果
*/
@Override
public int updateAppUser(AppUser appUser) {
return appUserMapper.updateAppUser(appUser);
}
/**
* 密码校验
*
* @param password 明文
* @param salt 盐
* @param hashPwd 密文
* @return boolean
*/
public boolean checkPassword(String password, String salt, String hashPwd) {
return hashPwd.equals(DigestUtils.md5DigestAsHex(password.concat(salt).getBytes()));
}
/**
* 随即生成盐
* 8位随机数
*
* @return salt 盐
*/
private String generateSalt() {
return RandomStringUtils.randomAlphanumeric(8);
}
public static void main(String[] args) {
AppUserServiceImpl appUserService = new AppUserServiceImpl();
String password = "123456";
String salt = appUserService.generateSalt();
System.out.printf("明文密码:%s%n", password);
System.out.printf("盐:%s%n", salt);
System.out.printf("密文密码:%s%n", DigestUtils.md5DigestAsHex(password.concat(salt).getBytes()));
}
}
7.创建 LoginAppUser 登录用户身份权限类
目录:ruoyi-common——>src——>main——>java——>com.ruoyi.common——>core——>domian——>model
注意:与 LoginUser 同级目录
package com.app.common.core.domain.model;
import com.alibaba.fastjson2.annotation.JSONField;
import com.app.common.core.domain.entity.AppUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* 登录用户身份权限
*
* @author ruoyi
*/
public class LoginAppUser implements UserDetails {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 用户唯一标识
*/
private String token;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 用户信息
*/
private AppUser appUser;
public LoginAppUser() {
}
public LoginAppUser(AppUser appUser) {
this.appUser = appUser;
}
public LoginAppUser(Long userId, AppUser appUser) {
this.userId = userId;
this.appUser = appUser;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
@JSONField(serialize = false)
@Override
public String getPassword() {
return appUser.getPassword();
}
@Override
public String getUsername() {
return appUser.getUserName();
}
/**
* 账户是否未过期,过期无法验证
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isEnabled() {
return true;
}
public Long getLoginTime() {
return loginTime;
}
public void setLoginTime(Long loginTime) {
this.loginTime = loginTime;
}
public String getIpaddr() {
return ipaddr;
}
public void setIpaddr(String ipaddr) {
this.ipaddr = ipaddr;
}
public String getLoginLocation() {
return loginLocation;
}
public void setLoginLocation(String loginLocation) {
this.loginLocation = loginLocation;
}
public String getBrowser() {
return browser;
}
public void setBrowser(String browser) {
this.browser = browser;
}
public String getOs() {
return os;
}
public void setOs(String os) {
this.os = os;
}
public Long getExpireTime() {
return expireTime;
}
public void setExpireTime(Long expireTime) {
this.expireTime = expireTime;
}
public AppUser getAppUser() {
return appUser;
}
public void setAppUser(AppUser appUser) {
this.appUser = appUser;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
}
8.创建 LoginAppBody 用户登录对象类
目录:ruoyi-common——>src——>main——>java——>com.ruoyi.common——>core——>domian——>model
注意:与 LoginBody 同级目录
package com.app.common.core.domain.model;
/**
* 用户登录对象
*
* @author ruoyi
*/
public class LoginAppBody
{
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}
9.添加通用常量信息 Constants & CacheConstants 常量信息
Constants
目录:ruoyi-common——>src——>main——>java——>com.ruoyi.common——>constant
/**
* APP令牌前缀
*/
public static final String LOGIN_APP_USER_KEY = "login_app_user_key";
/**
* APP接口前缀
*/
public static final String APP_URI = "/app/";
图示:
CacheConstants
目录:ruoyi-common——>src——>main——>java——>com.ruoyi.common——>constant
public static final String LOGIN_APP_TOKEN_KEY = "login_app_tokens:";
图示:
10.创建 AppTokenService TOKEN处理类
目录:ruoyi-framework——>src——>main——>java——>com.ruoyi.framework——>web——>service
注意:与 TokenService 同级目录
package com.app.framework.web.service;
import com.app.common.constant.CacheConstants;
import com.app.common.constant.Constants;
import com.app.common.core.domain.model.LoginAppUser;
import com.app.common.core.redis.RedisCache;
import com.app.common.utils.ServletUtils;
import com.app.common.utils.StringUtils;
import com.app.common.utils.ip.AddressUtils;
import com.app.common.utils.ip.IpUtils;
import com.app.common.utils.uuid.IdUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* token验证处理
*
* @author ruoyi
*/
@Component
public class AppTokenService {
// 令牌自定义标识
@Value("${token.header}")
private String header;
// 令牌秘钥
@Value("${token.secret}")
private String secret;
// 令牌有效期(默认30分钟)
@Value("${token.expireTime}")
private int expireTime;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
@Resource
private RedisCache redisCache;
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginAppUser getLoginAppUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token)) {
try {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_APP_USER_KEY);
String userKey = getTokenKey(uuid);
LoginAppUser loginAppUser = redisCache.getCacheObject(userKey);
return loginAppUser;
} catch (Exception e) {
}
}
return null;
}
/**
* 设置用户身份信息
*/
public void setLoginAppUser(LoginAppUser loginAppUser) {
if (StringUtils.isNotNull(loginAppUser) && StringUtils.isNotEmpty(loginAppUser.getToken())) {
refreshToken(loginAppUser);
}
}
/**
* 删除用户身份信息
*/
public void delLoginAppUser(String token) {
if (StringUtils.isNotEmpty(token)) {
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
}
/**
* 创建令牌
*
* @param loginAppUser 用户信息
* @return 令牌
*/
public String createAppToken(LoginAppUser loginAppUser) {
String token = IdUtils.fastUUID();
loginAppUser.setToken(token);
setUserAgent(loginAppUser);
refreshToken(loginAppUser);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_APP_USER_KEY, token);
return createToken(claims);
}
/**
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
*
* @param loginAppUser
* @return 令牌
*/
public void verifyToken(LoginAppUser loginAppUser) {
long expireTime = loginAppUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
refreshToken(loginAppUser);
}
}
/**
* 刷新令牌有效期
*
* @param loginAppUser 登录信息
*/
public void refreshToken(LoginAppUser loginAppUser) {
loginAppUser.setLoginTime(System.currentTimeMillis());
loginAppUser.setExpireTime(loginAppUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginAppUser.getToken());
redisCache.setCacheObject(userKey, loginAppUser, expireTime, TimeUnit.MINUTES);
}
/**
* 设置用户代理信息
*
* @param loginAppUser 登录信息
*/
public void setUserAgent(LoginAppUser loginAppUser) {
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr();
loginAppUser.setIpaddr(ip);
loginAppUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginAppUser.setBrowser(userAgent.getBrowser().getName());
loginAppUser.setOs(userAgent.getOperatingSystem().getName());
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims) {
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.getSubject();
}
/**
* 获取请求token
*
* @param request
* @return token
*/
private String getToken(HttpServletRequest request) {
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private String getTokenKey(String uuid) {
return CacheConstants.LOGIN_APP_TOKEN_KEY + uuid;
}
}
11.创建 AppLoginService 登录校验方法类
目录:ruoyi-framework——>src——>main——>java——>com.ruoyi.framework——>web——>service
注意:与 SysLoginService 同级目录
package com.app.framework.web.service;
import com.app.common.core.domain.entity.AppUser;
import com.app.common.core.domain.model.LoginAppUser;
import com.app.common.exception.ServiceException;
import com.app.common.utils.DateUtils;
import com.app.common.utils.ip.IpUtils;
import com.app.system.service.IAppUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 登录校验方法
*
* @author ruoyi
*/
@Component
public class AppLoginService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private AppTokenService appTokenService;
@Resource
private IAppUserService appUserSevice;
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @return 结果
*/
public String login(String username, String password) {
AppUser appUser = appUserSevice.selectAppUserByUserName(username);
if (appUser == null) {
throw new ServiceException("用户不存在");
}
if (!appUserSevice.checkPassword(password, appUser.getSalt(), appUser.getPassword())) {
throw new ServiceException("密码不正确");
}
LoginAppUser loginAppUser = new LoginAppUser(appUser.getUserId(), appUser);
recordLoginInfo(loginAppUser.getUserId());
// 生成token
return appTokenService.createAppToken(loginAppUser);
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
private void recordLoginInfo(Long userId) {
AppUser appUser = new AppUser();
appUser.setUserId(userId);
appUser.setLoginIp(IpUtils.getIpAddr());
appUser.setLoginDate(DateUtils.getNowDate());
appUserSevice.updateAppUser(appUser);
}
}
12.修改JwtAuthenticationTokenFilter token过滤器 验证token有效性
目录:ruoyi-framework——>src——>main——>java——>com.ruoyi.framework——>security——>filter
package com.app.framework.security.filter;
import java.io.IOException;
import java.util.Collection;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.app.common.constant.Constants;
import com.app.common.core.domain.model.LoginAppUser;
import com.app.framework.web.service.AppTokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.app.common.core.domain.model.LoginUser;
import com.app.common.utils.SecurityUtils;
import com.app.common.utils.StringUtils;
import com.app.framework.web.service.TokenService;
/**
* token过滤器 验证token有效性
*
* @author ruoyi
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private TokenService tokenService;
@Resource
private AppTokenService appTokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if (request.getRequestURI().contains(Constants.APP_URI)) {
logger.info("前台接口请求:{}", request.getRequestURI());
LoginAppUser loginAppUser = appTokenService.getLoginAppUser(request);
if (StringUtils.isNotNull(loginAppUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
appTokenService.verifyToken(loginAppUser);
auth(loginAppUser, loginAppUser.getAuthorities(), request);
}
} else {
logger.info("后台接口请求:{}", request.getRequestURI());
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
tokenService.verifyToken(loginUser);
auth(loginUser, loginUser.getAuthorities(), request);
}
}
chain.doFilter(request, response);
}
/**
* 验证token
*
* @param obj 登录信息
* @param authorities 已授予的权限集合
* @param request 网络请求
*/
private void auth(Object obj, Collection<? extends GrantedAuthority> authorities, HttpServletRequest request) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(obj, null, authorities);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
13.安全服务工具类 SecurityUtils 添加信息
目录:ruoyi-common——>src——>main——>java——>com.ruoyi.common——>utils
/**
* 获取APP用户
**/
public static LoginAppUser getLoginAppUser()
{
try
{
return (LoginAppUser) getAuthentication().getPrincipal();
}
catch (Exception e)
{
throw new ServiceException("获取APP用户信息异常", HttpStatus.UNAUTHORIZED);
}
}
图示:
14.web层通用数据类 BaseController 添加信息
目录:ruoyi-common——>src——>main——>java——>com.ruoyi.common——>core——>controller
/**
* 获取APP用户缓存信息
*/
public LoginAppUser getLoginAppUser()
{
return SecurityUtils.getLoginAppUser();
}
15.创建 AppLoginController 控制器
目录:ruoyi-admin——>src——>main——>java——>com.ruoyi——>web——>controller——>system
注意:与 SysUserController 同级目录
package com.app.web.controller.system;
import com.app.common.annotation.Anonymous;
import com.app.common.constant.Constants;
import com.app.common.core.controller.BaseController;
import com.app.common.core.domain.AjaxResult;
import com.app.common.core.domain.entity.AppUser;
import com.app.common.core.domain.model.LoginAppBody;
import com.app.framework.web.service.AppLoginService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 登录验证
*
* @author ruoyi
*/
@Api("APP登录管理")
@RestController
@RequestMapping("/app")
public class AppLoginController extends BaseController {
@Resource
private AppLoginService appLoginService;
@ApiOperation("登录")
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginAppBody loginAppBody) {
AjaxResult ajax = AjaxResult.success();
// 生成令牌
String token = appLoginService.login(loginAppBody.getUsername(), loginAppBody.getPassword());
ajax.put(Constants.TOKEN, token);
return ajax;
}
@ApiOperation("根据token获取用户信息")
@GetMapping("/userInfo")
@Anonymous
public AjaxResult userInfo() {
AppUser appUser = getLoginAppUser().getAppUser();
AjaxResult ajax = AjaxResult.success();
ajax.put("appUser", appUser);
return ajax;
}
}
16.SecurityConfig添加匿名访问地址
目录:ruoyi-framework——>src——>main——>java——>com.ruoyi.framework——>config
// 对于APP登录login 允许匿名访问
.antMatchers("/app/login").permitAll()
图示:
17.测试接口
使用Apifox测试接口
登录接口
登录成功后返回token
根据token获取用户信息
携带token访问,JwtAuthenticationTokenFilter token过滤器会拦截所有请求,验证token的有效性,从而获取用户信息
小程序登录
1.小程序登录和APP登录区别:
注意:如果您直接跳到此处观看小程序登录,一定要将上面APP登录时的代码补充完整
小程序登录和APP登录认证方式是一样的,可以共用上述的认证流程。
区别就是APP使用的是用户名,密码,手机号,验证码等参数进行登录。
小程序也可以使用上述参数的形式进行登录,但是现在大部分都是一键登录,下面我们分析一下小程序一键登录流程。
2.小程序登录流程
微信小程序的登录接口
根据微信官方文档,微信有提供自己的登录接口,可用于现有的微信用户,并且授予基本信息
整体流程
- 前端通过wx.login 获取 js_code
- 将获取的js_code发送给后端
- 后端拿到js_code,小程序 appId,小程序 appSecret,grant_type(参数默认写死为authorization_code)
- 将上述四个参数通过GET请求发送给微信小程序登录接口,请求地址:https://api.weixin.qq.com/sns/jscode2session
- 请求成功后拿到响应参数:session_key(会话密钥),unionid(用户在开放平台的唯一标识符),errmsg(错误信息),openid(用户唯一标识),errcode(错误代码)
- 我们可以拿openid(用户唯一标识)当作数据库表中的唯一字段,如上述APP登录时候的user_name(用户账号)
- 后续登陆时拿openid进行认证即可
3.在配置文件中添加小程序参数
目录:ruoyi-admin——>src——>resource——>application.yml
注意:请改写为自己的小程序参数 appId & appSecret
#微信小程序配置
wx:
minApp:
appId: wxedxxxxxxxx7cxx1x
appSecret: 0ccxxxx54d1xxxx88xa9xxxxde07xxx8
图示:
4.修改AppUserMapper.xml,新增insert语句
目录:ruoyi-system——>src——>main——>resources——>mapper.system
<insert id="insertAppUser" parameterType="AppUser" useGeneratedKeys="true" keyProperty="userId">
insert into app_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userName != null and userName != ''">user_name,</if>
<if test="nickName != null and nickName != ''">nick_name,</if>
<if test="email != null">email,</if>
<if test="mobile != null">mobile,</if>
<if test="sex != null">sex,</if>
<if test="avatar != null">avatar,</if>
<if test="password != null">password,</if>
<if test="salt != null">salt,</if>
<if test="status != null">status,</if>
<if test="delFlag != null">del_flag,</if>
<if test="loginIp != null">login_ip,</if>
<if test="loginDate != null">login_date,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userName != null and userName != ''">#{userName},</if>
<if test="nickName != null and nickName != ''">#{nickName},</if>
<if test="email != null">#{email},</if>
<if test="mobile != null">#{mobile},</if>
<if test="sex != null">#{sex},</if>
<if test="avatar != null">#{avatar},</if>
<if test="password != null">#{password},</if>
<if test="salt != null">#{salt},</if>
<if test="status != null">#{status},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="loginIp != null">#{loginIp},</if>
<if test="loginDate != null">#{loginDate},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
5.修改AppUserMapper数据层增加新增用户接口
目录:ruoyi-system——>src——>main——>java——>com.ruoyi.system——>mapper
/**
* 新增用户信息
*
* @param appUser 用户信息
* @return 结果
*/
public int insertAppUser(AppUser appUser);
6.修改IAppUserService增加新增用户接口
目录:ruoyi-system——>src——>main——>java——>com.ruoyi.system——>service
/**
* 新增用户
* @param appUser
* @return
*/
int insertAppUser(AppUser appUser);
7.修改AppUserServiceImpl 业务实现层
目录:ruoyi-system——>src——>main——>java——>com.ruoyi.system——>service——>impl
/**
* 新增用户
* @param appUser
* @return
*/
@Override
public int insertAppUser(AppUser appUser) {
return appUserMapper.insertAppUser(appUser);
}
8.修改AppLoginService
目录:ruoyi-framework——>src——>main——>java——>com.ruoyi.framework——>web——>service
修改内容:
1.新增成员变量 appId&appSecret,使用@Value注解读取配置文件参数并赋值给对应得变量
2.新增minLogin方法实现小程序登录
package com.app.framework.web.service;
import com.alibaba.fastjson2.JSONObject;
import com.app.common.core.domain.entity.AppUser;
import com.app.common.core.domain.model.LoginAppUser;
import com.app.common.exception.ServiceException;
import com.app.common.utils.DateUtils;
import com.app.common.utils.StringUtils;
import com.app.common.utils.http.HttpUtils;
import com.app.common.utils.ip.IpUtils;
import com.app.system.service.IAppUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 登录校验方法
*
* @author ruoyi
*/
@Component
public class AppLoginService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private AppTokenService appTokenService;
@Resource
private IAppUserService appUserSevice;
@Value("${wx.minApp.appId}")
private String appId;
@Value("${wx.minApp.appSecret}")
private String appSecret;
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @return 结果
*/
public String login(String username, String password) {
AppUser appUser = appUserSevice.selectAppUserByUserName(username);
if (appUser == null) {
throw new ServiceException("用户不存在");
}
if (!appUserSevice.checkPassword(password, appUser.getSalt(), appUser.getPassword())) {
throw new ServiceException("密码不正确");
}
LoginAppUser loginAppUser = new LoginAppUser(appUser.getUserId(), appUser);
recordLoginInfo(loginAppUser.getUserId());
// 生成token
return appTokenService.createAppToken(loginAppUser);
}
/**
* 小程序登录
* @param code
* @return
*/
public String minLogin(String code) {
//1根据code执行小程序登录获取 openId
String url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
String replaceUrl = url.replace("{0}",appId).replace("{1}",appSecret).replace("{2}",code);
System.out.println(replaceUrl);
JSONObject jsonObject = JSONObject.parseObject(HttpUtils.sendGet(replaceUrl));
if(jsonObject.get("errcode")!=null){
throw new ServiceException(String.format("获取微信授权信息失败,错误编码:%s,错误信息:%s",String.valueOf(jsonObject.get("errcode")),(String)jsonObject.get("errmsg")));
}
//2根据openId判断用户是否存在 不存在自动注册 openId对应数据库表user_name
AppUser appUser = appUserSevice.selectAppUserByUserName(String.valueOf(jsonObject.get("openid")));
if (appUser == null) {
appUser = new AppUser();
appUser.setUserName(String.valueOf(jsonObject.get("openid")));
appUserSevice.insertAppUser(appUser);
}
//3生成token
LoginAppUser loginAppUser = new LoginAppUser(appUser.getUserId(), appUser);
recordLoginInfo(loginAppUser.getUserId());
return appTokenService.createAppToken(loginAppUser);
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
private void recordLoginInfo(Long userId) {
AppUser appUser = new AppUser();
appUser.setUserId(userId);
appUser.setLoginIp(IpUtils.getIpAddr());
appUser.setLoginDate(DateUtils.getNowDate());
appUserSevice.updateAppUser(appUser);
}
}
9.修改AppLoginController新增小程序登录
目录:ruoyi-admin——>src——>main——>java——>com.ruoyi——>web——>controller——>system
重要:方法接收的参数code是前端通过 wx.login 获取得 js_code
@ApiOperation("微信小程序登录")
@GetMapping("/minLogin")
public AjaxResult minLogin(String code){
AjaxResult ajax = AjaxResult.success();
// 生成令牌
String token = appLoginService.minLogin(code);
ajax.put(Constants.TOKEN, token);
return ajax;
}
10.测试接口
使用Apifox测试
登录接口:
根据token获取用户信息
注意:Headers中要携带token访问
实现效果
第一次登录没有查询到用户,自动注册,将用户信息封装成token返回
再次登录,根据openid查询数据库,将查询到的用户信息封装成token返回