springboot + shiro + jwt 实现前后端分离
程序员文章站
2022-03-15 11:45:35
...
sql脚本
/*
Navicat Premium Data Transfer
Source Server : 127.0.0.1
Source Server Type : MySQL
Source Server Version : 50726
Source Host : 127.0.0.1:3306
Source Schema : financial
Target Server Type : MySQL
Target Server Version : 50726
File Encoding : 65001
Date: 24/11/2021 15:23:12
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
`url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`type` int(11) NULL DEFAULT NULL COMMENT '类型 1:目录 2:菜单 3:按钮',
`icon` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单图标',
`system_type` int(255) NULL DEFAULT NULL COMMENT '系统类型 1:管理员端 2:client 端',
`order_num` int(11) NULL DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单管理' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色名称',
`remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID',
`company_id` bigint(20) NULL DEFAULT NULL COMMENT '公司id',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
`create_by` bigint(11) NULL DEFAULT NULL COMMENT '创建人',
`update_date` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`update_by` bigint(11) NULL DEFAULT NULL COMMENT '修改人',
`del_flag` tinyint(1) NULL DEFAULT NULL COMMENT '是否删除 0:否,1:是',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
`menu_id` bigint(20) NULL DEFAULT NULL COMMENT '菜单ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色与菜单对应关系' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
`salt` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '盐',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
`status` tinyint(4) NULL DEFAULT NULL COMMENT '状态 0:禁用 1:正常',
`dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID',
`company_id` bigint(20) NULL DEFAULT NULL COMMENT '公司id',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
`create_by` bigint(11) NULL DEFAULT NULL COMMENT '创建人',
`update_date` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`update_by` bigint(11) NULL DEFAULT NULL COMMENT '修改人',
`del_flag` tinyint(1) NULL DEFAULT NULL COMMENT '是否删除 0:否,1:是',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统用户' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与角色对应关系' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
依赖
springboot 版本号为 2.5.6
<properties>
<shiro.version>1.6.0</shiro.version>
<java-jwt.version>3.2.0</java-jwt.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>
</dependencies>
代码
ShiroConfig
package com.sun.financial.common.config;
import com.sun.financial.common.filter.JwtTokenFilter;
import com.sun.financial.common.filter.PermissionAuthFilter;
import com.sun.financial.modules.shiro.realm.UserRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Shiro的配置文件
*
* @author [email protected]
*/
@Configuration
public class ShiroConfig {
/**
* 注入 securityManager
* @param userRealm
* @return
*/
@Bean("securityManager")
public SessionsSecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置自定义 realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* @param securityManager
* @return
*/
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new LinkedHashMap<>();
//设置我们自定义的JWT过滤器
filterMap.put("jwtToken", new JwtTokenFilter());
filterMap.put("permission", new PermissionAuthFilter());
shiroFilter.setFilters(filterMap);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//API 文档放行
filterChainDefinitionMap.put("/doc/index.html","anon");
filterChainDefinitionMap.put("/doc/AllInOne.css","anon");
filterChainDefinitionMap.put("/doc/debug.js","anon");
filterChainDefinitionMap.put("/doc/font.css","anon");
filterChainDefinitionMap.put("/doc/highlight.min.js","anon");
filterChainDefinitionMap.put("/doc/jquery.min.js","anon");
filterChainDefinitionMap.put("/doc/xt256.min.css","anon");
//不需要检验的接口
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/logout", "anon");
//除以上之外全部校验
filterChainDefinitionMap.put("/**", "jwtToken,permission");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilter.setSecurityManager(securityManager);
return shiroFilter;
}
/**
* 添加注解支持
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
JwtTokenFilter
package com.sun.financial.common.filter;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.sun.financial.common.constant.BaseConstant;
import com.sun.financial.common.utils.JwtUtils;
import com.sun.financial.common.utils.Result;
import com.sun.financial.common.utils.ResultCode;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JwtTokenFilter extends AccessControlFilter {
/**
*
* 如果带有 token,则对 token 进行检查,否则直接通过
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader(BaseConstant.TOKEN);
//判断请求的请求头是否带上 "token"
if (StrUtil.isBlank(token)) {
return false;
}
if (!JwtUtils.verify(token)){
return false;
}
return true;
}
/**
* Processes requests where the subject was denied access as determined by the
* {@link #isAccessAllowed(ServletRequest, ServletResponse, Object) isAccessAllowed}
* method.
*
* @param request the incoming <code>ServletRequest</code>
* @param response the outgoing <code>ServletResponse</code>
* @return <code>true</code> if the request should continue to be processed; false if the subclass will
* handle/render the response directly.
* @throws Exception if there is an error processing the request.
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSONObject.toJSON(new Result<>(ResultCode.INVALID_TOKEN)));
return false;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
PermissionAuthFilter
package com.sun.financial.common.filter;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.sun.financial.common.config.SpringConfig;
import com.sun.financial.common.constant.BaseConstant;
import com.sun.financial.common.utils.JwtUtils;
import com.sun.financial.common.utils.Result;
import com.sun.financial.common.utils.ResultCode;
import com.sun.financial.modules.sys.entity.SysMenuEntity;
import com.sun.financial.modules.sys.service.SysMenuService;
import org.apache.shiro.web.filter.AccessControlFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;
public class PermissionAuthFilter extends AccessControlFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
boolean flag = true;
HttpServletRequest req = (HttpServletRequest) request;
String requestURI = req.getRequestURI();
System.out.println(requestURI);
String[] split = requestURI.substring(1).split("/");
if (split.length>0){
String token = req.getHeader(BaseConstant.TOKEN);
Long id = JwtUtils.getUserId(token);
SysMenuService sysMenuService = SpringConfig.getBean(SysMenuService.class);
List<SysMenuEntity> userMenuList = sysMenuService.getUserMenuList(id);
List<String> strings = userMenuList.parallelStream().filter(i -> StrUtil.isNotBlank(i.getPerms()))
.distinct().map(SysMenuEntity::getPerms).collect(Collectors.toList());
//路径格式为 /XX/XX/XX,可根据自己喜好更改
String concat = split[0].concat(":").concat(split[1].concat(":").concat(split[2]));
System.out.println(concat);
if (strings.contains(concat)){
flag = true;
}
}
return flag;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSONObject.toJSON(new Result<>(ResultCode.UNAUTHORIZED)));
return false;
}
}
JwtUtils
package com.sun.financial.common.utils;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sun.financial.common.config.SpringConfig;
import com.sun.financial.common.constant.BaseConstant;
import com.sun.financial.modules.sys.entity.SysUserEntity;
import java.util.Map;
public class JwtUtils {
// **
private static final String SECRET = "11111111111111111111";
// **
private static final String EXPIRE_TIME = "expire_time";
/**
* 生成 token
*/
public static String createToken(Map<String, Object> map) {
map.put(EXPIRE_TIME, System.currentTimeMillis() + BaseConstant.EXPIRE_TIME);
String token = JWTUtil.createToken(map, SECRET.getBytes());
return token;
}
/**
* 校验 token 是否正确
*/
public static boolean verify(String token) {
boolean verify = JWTUtil.verify(token, SECRET.getBytes());
if (!verify){
return false;
}
JWTPayload jwtPayload = getJWTPayload(token);
long expireTime = (long)jwtPayload.getClaim(EXPIRE_TIME);
long timeMillis = System.currentTimeMillis();
if (timeMillis>expireTime){
return false;
}
RedisUtils redisUtils = SpringConfig.getBean(RedisUtils.class);
String jwtToken = redisUtils.get(token,String.class);
if (jwtToken == null){
return false;
}
return verify;
}
/**
* 获得token中的信息,无需secret解密也能获得
*/
public static JWTPayload getJWTPayload(String token) {
final JWT jwt = JWTUtil.parseToken(token);
JWTPayload payload = jwt.getPayload();
return payload;
}
/**
* 获得token中的信息,无需secret解密也能获得
*/
public static SysUserEntity getSysUserEntity(String token) {
JWTPayload jwtPayload = getJWTPayload(token);
SysUserEntity sysUserEntity = JSONObject.parseObject(JSON.toJSONString(jwtPayload.getClaimsJson()), SysUserEntity.class);
return sysUserEntity;
}
/**
* 获取登陆账号id
* @param token
* @return
*/
public static Long getUserId(String token) {
SysUserEntity payload = getSysUserEntity(token);
Long id = payload.getId();
return id;
}
/**
* 获取登陆账号id
* @param token
* @return
*/
public static String getSalt(String token) {
SysUserEntity payload = getSysUserEntity(token);
String salt = payload.getSalt();
return salt;
}
/**
* 获取登陆账号 所在公司的id
* @param token
* @return
*/
public static Long getCompanyId(String token) {
SysUserEntity payload = getSysUserEntity(token);
Long id = payload.getCompanyId();
return id;
}
/**
* 获取登陆人username
* @param token
* @return
*/
public static String getUsername(String token) {
SysUserEntity payload = getSysUserEntity(token);
String id = payload.getUsername();
return id;
}
}
UserRealm
package com.sun.financial.modules.shiro.realm;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sun.financial.common.constant.BaseConstant;
import com.sun.financial.common.utils.ShiroUtils;
import com.sun.financial.modules.sys.mapper.SysMenuMapper;
import com.sun.financial.modules.sys.mapper.SysUserMapper;
import com.sun.financial.modules.sys.entity.SysMenuEntity;
import com.sun.financial.modules.sys.entity.SysUserEntity;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* 认证
*
* @author [email protected]
*/
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysMenuMapper sysMenuMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
//查询用户信息
SysUserEntity user = sysUserMapper.selectOne(new QueryWrapper<SysUserEntity>().lambda().eq(SysUserEntity::getUsername, token.getUsername()));
//账号不存在
if(user == null) {
throw new UnknownAccountException("账号不存在");
}
//账号锁定
if(user.getStatus() == 0){
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
return info;
}
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
shaCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
shaCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
super.setCredentialsMatcher(shaCredentialsMatcher);
}
}
SysLoginController
package com.sun.financial.modules.shiro.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sun.financial.common.annotation.SysLog;
import com.sun.financial.common.constant.BaseConstant;
import com.sun.financial.common.utils.*;
import com.sun.financial.modules.shiro.request.LoginRequest;
import com.sun.financial.modules.sys.entity.SysUserEntity;
import com.sun.financial.modules.sys.service.SysUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Map;
/**
* 登录相关
*
* @author [email protected]
*/
@RestController
public class SysLoginController {
@Autowired
private RedisUtils redisUtils;
@Autowired
private SysUserService sysUserService;
/**
* 登录
* @param request
* @return
*/
@PostMapping(value = "login")
public Result login(@Valid @RequestBody LoginRequest request) {
try{
Subject subject = ShiroUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(request.getUsername(), request.getPassword());
subject.login(token);
SysUserEntity user = sysUserService.getOne(new QueryWrapper<SysUserEntity>().lambda().eq(SysUserEntity::getUsername, token.getUsername()));
String jsonString = JSON.toJSONString(user);
Map<String,Object> map = JSONObject.parseObject(jsonString);
map.remove("password");
String jwtToken = JwtUtils.createToken(map);
redisUtils.set(jwtToken,jsonString, BaseConstant.EXPIRE_TIME);
return Result.success(jwtToken);
}catch (UnknownAccountException e) {
return Result.error(e.getMessage());
}catch (IncorrectCredentialsException e) {
return Result.error("密码不正确");
}catch (LockedAccountException e) {
return Result.error(e.getMessage());
}catch (AuthenticationException e) {
return Result.error("账号或密码错误!");
}
}
/**
* 退出登录
* @return
*/
@PostMapping(value = "logout")
public Result logout(@RequestHeader String token) {
ShiroUtils.logout();
redisUtils.delete(token);
return Result.success();
}
}
下一篇: 大型网站架构技能图谱(Java版)
推荐阅读
-
SpringBoot+Vue.js实现前后端分离的文件上传功能
-
vue+springboot前后端分离实现单点登录跨域问题解决方法
-
shiro,基于springboot,基于前后端分离,从登录认证到鉴权,从入门到放弃
-
ASP .NET + Angular前后端分离实现简单投票系统(JWT登录用户认证)
-
ajax + vue + springboot + Restful实现前后端分离项目
-
Springboot + Vue + shiro 实现前后端分离、权限控制
-
jwt,spring security ,feign,zuul,eureka 前后端分离 整合 实现 简单 权限管理系统 与 用户认证的实现
-
Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码
-
SpringSecurity+JWT实现前后端分离的使用详解
-
部署vue+Springboot前后端分离项目的步骤实现