SpringSecurity前后端分离

从上至下操作,直接上手SpringSecurity

文章目录

  • SpringSecurity前后端分离
    • 1、项目环境
      • maven依赖
      • 数据库表
    • 2、自定义UserService接口
    • 3、屏蔽Spring Security默认重定向登录页面以实现前后端分离功能
      • 1、实现登录成功/失败、登出处理逻辑
        • 1、表单形式登录
          • 一、自定义登录接口
          • 二、自定义登录成功,失败的错误逻辑处理
          • 三、自定义用户未登录逻辑
          • 四、退出登录
      • 2、使用JSON格式进行登录
    • 4、实现基于数据库的动态权限控制
  • SpringSecurity+JWT实现token登录
  • 结合验证码实现登录
    • 生成验证码
    • 结合验证码登录

1、项目环境

maven依赖

使用的是Mybatis-Plus做数据库操作

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.15</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

数据库表

1、用户表(sys_user)

密码是加密后的(123456)

/*Navicat Premium Data TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 10/02/2023 17:04:23
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (`id` int NOT NULL AUTO_INCREMENT,`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '账号',`user_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户密码',`last_login_time` datetime NULL DEFAULT NULL COMMENT '上一次登录时间',`enabled` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否可用。默认为1(可用)',`not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '是否过期。默认为1(没有过期)',`account_not_locked` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否锁定。默认为1(没有锁定)',`credentials_not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '证书(密码)是否过期。默认为1(没有过期)',`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',`create_user` int NULL DEFAULT NULL COMMENT '创建人',`update_user` int NULL DEFAULT NULL COMMENT '修改人',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'user1', '用户1', '$2a$10$47lsFAUlWixWG17Ca3M/r.EPJVIb7Tv26ZaxhzqN65nXVcAhHQM4i', '2019-09-04 20:25:36', 1, 1, 1, 1, '2019-08-29 06:28:36', '2019-09-04 20:25:36', 1, 1);
INSERT INTO `sys_user` VALUES (2, 'user2', '用户2', '$2a$10$uSLAeON6HWrPbPCtyqPRj.hvZfeM.tiVDZm24/gRqm4opVze1cVvC', '2019-09-05 00:07:12', 1, 1, 1, 1, '2019-08-29 06:29:24', '2019-09-05 00:07:12', 1, 2);SET FOREIGN_KEY_CHECKS = 1;

2、权限表(sys_permission)

/*Navicat Premium Data TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 10/02/2023 17:19:56
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',`permission_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限code',`permission_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限名',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'create_user', '创建用户');
INSERT INTO `sys_permission` VALUES (2, 'query_user', '查看用户');
INSERT INTO `sys_permission` VALUES (3, 'delete_user', '删除用户');
INSERT INTO `sys_permission` VALUES (4, 'modify_user', '修改用户');SET FOREIGN_KEY_CHECKS = 1;

3、角色表(sys_role)

/*Navicat Premium Data TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 10/02/2023 17:20:23
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',`role_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色code',`role_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名',`role_description` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色说明',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '管理员', '管理员,拥有所有权限');
INSERT INTO `sys_role` VALUES (2, 'user', '普通用户', '普通用户,拥有部分权限');SET FOREIGN_KEY_CHECKS = 1;

4、角色权限关系表(sys_role_permission_relation)

/*Navicat Premium Data TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 10/02/2023 17:21:29
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for sys_role_permission_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission_relation`;
CREATE TABLE `sys_role_permission_relation`  (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',`role_id` int NULL DEFAULT NULL COMMENT '角色id',`permission_id` int NULL DEFAULT NULL COMMENT '权限id',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-权限关联关系表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_role_permission_relation
-- ----------------------------
INSERT INTO `sys_role_permission_relation` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission_relation` VALUES (2, 1, 2);
INSERT INTO `sys_role_permission_relation` VALUES (3, 1, 3);
INSERT INTO `sys_role_permission_relation` VALUES (4, 1, 4);SET FOREIGN_KEY_CHECKS = 1;

5、用户角色关系表(sys_user_role_relation)

/*Navicat Premium Data TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 10/02/2023 17:22:13
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for sys_user_role_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role_relation`;
CREATE TABLE `sys_user_role_relation`  (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',`user_id` int NULL DEFAULT NULL COMMENT '用户id',`role_id` int NULL DEFAULT NULL COMMENT '角色id',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色关联关系表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_user_role_relation
-- ----------------------------
INSERT INTO `sys_user_role_relation` VALUES (1, 1, 1);
INSERT INTO `sys_user_role_relation` VALUES (2, 2, 2);SET FOREIGN_KEY_CHECKS = 1;

6、请求路径表(sys_request_path)

/*Navicat Premium Data TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 10/02/2023 17:23:23
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for sys_request_path
-- ----------------------------
DROP TABLE IF EXISTS `sys_request_path`;
CREATE TABLE `sys_request_path`  (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',`url` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求路径',`description` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路径描述',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '请求路径' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_request_path
-- ----------------------------
INSERT INTO `sys_request_path` VALUES (1, '/demo/getUser', '查询用户');SET FOREIGN_KEY_CHECKS = 1;

7、请求路径权限关系表(sys_request_path_permission_relation)

/*Navicat Premium Data TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 10/02/2023 17:23:29
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for sys_request_path_permission_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_request_path_permission_relation`;
CREATE TABLE `sys_request_path_permission_relation`  (`id` int NULL DEFAULT NULL COMMENT '主键id',`url_id` int NULL DEFAULT NULL COMMENT '请求路径id',`permission_id` int NULL DEFAULT NULL COMMENT '权限id'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '路径权限关联表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_request_path_permission_relation
-- ----------------------------
INSERT INTO `sys_request_path_permission_relation` VALUES (1, 1, 2);SET FOREIGN_KEY_CHECKS = 1;

说明:

角色1对应的是管理员角色,有全部权限(这里主要演示的是query_user权限)

角色2对应的是用户,没有任何权限

2、自定义UserService接口

实现从数据库中获取用户的信息,自定义登录逻辑

@Service
public class UserService implements UserDetailsService{@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserRoleMapper userRoleMapper;@Autowiredprivate RolePermissionMapper rolePermissionMapper;@Autowiredprivate PermissionMapper permissionMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//需要构造出 org.springframework.security.core.userdetails.User 对象并返回
/***         String username:用户名*         String password: 密码*         boolean enabled: 账号是否可用*         boolean accountNonExpired:账号是否没有过期*         boolean credentialsNonExpired:密码是否没有过期*         boolean accountNonLocked:账号是否没有被锁定*         Collection<? extends GrantedAuthority> authorities):用户权限列表*///1、根据用户名查询用户信息QueryWrapper<UserEntity> queryWrapper=new QueryWrapper<>();queryWrapper.eq("account",username);UserEntity userEntity = userMapper.selectOne(queryWrapper);//2、根据用户名查询用户的权限信息//查询userIdInteger id = userEntity.getId();//查询权限idQueryWrapper<UserRoleRelation> queryWrapper1=new QueryWrapper<>();queryWrapper1.eq("user_id", id);UserRoleRelation userRoleRelation = userRoleMapper.selectOne(queryWrapper1);Integer roleId = userRoleRelation.getRoleId();//根据角色信息查询对应的权限信息//查询权限idQueryWrapper<RolePermissionRelation> rolePermissionRelationQueryWrapper = new QueryWrapper<>();rolePermissionRelationQueryWrapper.eq("role_id", roleId);List<RolePermissionRelation> rolePermissionRelations = rolePermissionMapper.selectList(rolePermissionRelationQueryWrapper);//权限列表List<GrantedAuthority> grantedAuthorities = new ArrayList<>();for (RolePermissionRelation rolePermissionRelation : rolePermissionRelations) {PermissionEntity permissionEntity = permissionMapper.selectById(rolePermissionRelation.getPermissionId());GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permissionEntity.getPermissionCode());grantedAuthorities.add(grantedAuthority);}//3、构造出我们需要的org.springframework.security.core.userdetails.User对象User user = new User(userEntity.getUserName(), userEntity.getPassword(), userEntity.getEnabled() == 1 ? true : false, userEntity.getNotExpired() == 1 ? true : false, userEntity.getCredentialsNotExpired() == 1 ? true : false, userEntity.getAccountNotLocked() == 1 ? true : false, grantedAuthorities);return user;}

进行配置使用我们自定义的UserService,并设置密码加密

/*** @ClassName WebSecurityConfig* @Author ylh* @Date 2023/2/7 21:28* @Description*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic UserDetailsService userDetailsService() {//获取用户账号密码及权限信息return new UserService();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//获取用户账号密码及权限信息auth.userDetailsService(userDetailsService());}// 设置默认的加密方式(强hash方式加密),注入就行@Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

3、屏蔽Spring Security默认重定向登录页面以实现前后端分离功能

1、实现登录成功/失败、登出处理逻辑

1、表单形式登录

一、自定义登录接口

这里是使用表单进行登录,使用JSON进行登录在下面有介绍

默认的登录接口的/login,下面我们使用自定义的路径

在WebSecurityConfig类中重写configure方法(注意参数是HttpSecurity)

@Override
protected void configure(HttpSecurity http)

测试:

使用postman测试http://localhost:8080/user/login?username=user1&password=123456

注意:要使用post请求

二、自定义登录成功,失败的错误逻辑处理

创建两个Component

/*** @Author: ylh* @Description: 登录成功处理逻辑*/
@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, org.springframework.security.core.Authentication authentication) throws IOException, ServletException {//此处还可以进行一些处理,比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限,//进而前台动态的控制菜单的显示等,具体根据自己的业务需求进行扩展//返回json数据JsonResult result = ResultTool.success();//处理编码方式,防止中文乱码的情况httpServletResponse.setContentType("text/json;charset=utf-8");//塞到HttpServletResponse中返回给前台httpServletResponse.getWriter().write(JSON.toJSONString(result));}
}
/*** @Author:* @Description: 登录失败处理逻辑*/
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException, IOException {//返回json数据JsonResult result = null;if (e instanceof AccountExpiredException) {//账号过期result = ResultTool.fail(ResultCode.USER_ACCOUNT_EXPIRED);} else if (e instanceof BadCredentialsException) {//密码错误result = ResultTool.fail(ResultCode.USER_CREDENTIALS_ERROR);} else if (e instanceof CredentialsExpiredException) {//密码过期result = ResultTool.fail(ResultCode.USER_CREDENTIALS_EXPIRED);} else if (e instanceof DisabledException) {//账号不可用result = ResultTool.fail(ResultCode.USER_ACCOUNT_DISABLE);} else if (e instanceof LockedException) {//账号锁定result = ResultTool.fail(ResultCode.USER_ACCOUNT_LOCKED);} else if (e instanceof InternalAuthenticationServiceException) {//用户不存在result = ResultTool.fail(ResultCode.USER_ACCOUNT_NOT_EXIST);}else{//其他错误result = ResultTool.fail(ResultCode.COMMON_FAIL);}//处理编码方式,防止中文乱码的情况httpServletResponse.setContentType("text/json;charset=utf-8");//塞到HttpServletResponse中返回给前台httpServletResponse.getWriter().write(JSON.toJSONString(result));}
}

在配置类中进行配置

先注入进来

@Autowired
private CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private CustomizeAuthenticationFailureHandler authenticationFailureHandler;

设置
替换原来的拦截器,使用我们自定义的

三、自定义用户未登录逻辑

创建一个接口

@GetMapping("/getUser")
public String getUser(){return "查询用户";
}

自定义component

/*** @Author: ylh* @Description: 未登录的异常处理*/
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {//自定义返回信息返回JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);httpServletResponse.setContentType("text/json;charset=utf-8");httpServletResponse.getWriter().write(JSON.toJSONString(result));}
}

注入到配置类中

@Autowired
private CustomizeAuthenticationEntryPoint authenticationEntryPoint;

进行相关 配置

设置权限,如果不设置,不用登录也可以访问(因为被我们重写了void configure(HttpSecurity http)这个方法)

http.authorizeRequests().antMatchers("/demo/getUser").hasAuthority("query_user")//为getUser接口设置权限.and()//异常处理(权限拒绝、登录失效等)//匿名用户访问无权限资源时的异常处理.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).

返回结果:

四、退出登录

springsecurity默认退出登录的url地址为http://localhost:8080/logout

创建Component

/*** @Author: ylh* @Description: 登出成功处理逻辑*/
@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException, IOException {JsonResult result = ResultTool.success();httpServletResponse.setContentType("text/json;charset=utf-8");httpServletResponse.getWriter().write(JSON.toJSONString(result));}
}

在配置类中

将CustomizeLogoutSuccessHandler注入进来

设置

//登出and().logout().permitAll().//允许所有用户logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑deleteCookies("JSESSIONID").//登出之后删除cookie

2、使用JSON格式进行登录

继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication方法

/*** @ClassName CustomAuthenticationFilter* @Author ylh* @Date 2023/2/9 21:39* @Description*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {ObjectMapper mapper = new ObjectMapper();UsernamePasswordAuthenticationToken authRequest = null;try (InputStream is = request.getInputStream()) {Map<String,String> authenticationBean = mapper.readValue(is, Map.class);authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.get("username"), authenticationBean.get("password"));} catch (IOException e) {e.printStackTrace();authRequest = new UsernamePasswordAuthenticationToken("", "");} finally {setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}else {return super.attemptAuthentication(request, response);}}
}

配置类中,创建一个Bean

@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter filter = new CustomAuthenticationFilter();filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();//登录成功的返回JsonResult result = ResultTool.success();out.write(new ObjectMapper().writeValueAsString(result));out.flush();out.close();}});filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();//设置登录失败的返回结果JsonResult result = ResultTool.fail();out.write(new ObjectMapper().writeValueAsString(result));out.flush();out.close();}});filter.setAuthenticationManager(authenticationManagerBean());return filter;
}

在configure(HttpSecurity http)方法中,替换原有的过滤器,使用我们自定义的过滤器

http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

使用JSON进行登录配置类中就不需要formLogin(),退出登录的方法是一样的

目前configure的内容

   @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/demo/getUser").hasAuthority("query_user").//为getUser接口设置权限and()
//                //异常处理(权限拒绝、登录失效等)页面不能进行重定向到登录页面了,
//                //匿名用户访问无权限资源时的异常处理.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
////登出.and().logout().permitAll().//允许所有用户logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑deleteCookies("JSESSIONID")//登出之后删除cookie.and().csrf().disable();http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}

4、实现基于数据库的动态权限控制

前面是直接在配置类中添加的权限控制

    http.authorizeRequests().antMatchers("/demo/getUser").hasAuthority("query_user").//为

现在是基于数据库实现,将配置类中的权限配置删除

1、编写权限拦截器


/*** @Author: ylh* @Description: 权限拦截器*/
@Service
public class CustomizeAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {@Autowiredprivate FilterInvocationSecurityMetadataSource securityMetadataSource;@Autowiredpublic void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {super.setAccessDecisionManager(accessDecisionManager);}@Overridepublic Class<?> getSecureObjectClass() {return FilterInvocation.class;}@Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {return this.securityMetadataSource;}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);invoke(fi);}public void invoke(FilterInvocation fi) throws IOException, ServletException {//fi里面有一个被拦截的url//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够InterceptorStatusToken token = super.beforeInvocation(fi);try {//执行下一个拦截器fi.getChain().doFilter(fi.getRequest(), fi.getResponse());} finally {super.afterInvocation(token, null);}}
}

2、安全元数据源FilterInvocationSecurityMetadataSource

package com.example.demo.service;import com.example.demo.entity.PermissionEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;import java.util.Collection;
import java.util.List;/*** @Author: Hutengfei* @Description:* @Date Create in 2019/9/3 21:06*/
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//    AntPathMatcher antPathMatcher = new AntPathMatcher();@AutowiredSysPermissionService sysPermissionService;@Overridepublic Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {//获取请求地址String requestUrl = ((FilterInvocation) o).getRequestUrl();//查询具体某个接口的权限List<PermissionEntity> permissionList =  sysPermissionService.selectListByPath(requestUrl);if(permissionList == null || permissionList.size() == 0){//请求路径没有配置权限,表明该请求接口可以任意访问return null;}//请求路径配置了权限String[] attributes = new String[permissionList.size()];for(int i = 0;i<permissionList.size();i++){attributes[i] = permissionList.get(i).getPermissionCode();}return SecurityConfig.createList(attributes);}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}

这里是sysPermissionService是查询路径所需要的权限,这里贴出来供大家参考

/*** @ClassName SysPermissionService* @Author ylh* @Date 2023/2/10 15:05* @Description*/
@Service
public class SysPermissionService {@Autowiredprivate RequestUrlMapper requestUrlMapper;@Autowiredprivate RequestPermissionMapper requestPermissionMapper;@Autowiredprivate PermissionMapper permissionMapper;public List<PermissionEntity> selectListByPath(String requestUrl){//根据URL查询对应权限List<PermissionEntity> list=new ArrayList<>();QueryWrapper<RequestUrlEntity> queryWrapper=new QueryWrapper();queryWrapper.eq("url",requestUrl);RequestUrlEntity requestUrlEntity = requestUrlMapper.selectOne(queryWrapper);if (requestUrlEntity!=null){//查询权限idQueryWrapper<RequestPermissionEntity> requestPermissionEntityQueryWrapper=new QueryWrapper();requestPermissionEntityQueryWrapper.eq("url_id",requestUrlEntity.getId());List<RequestPermissionEntity> requestPermissionEntities = requestPermissionMapper.selectList(requestPermissionEntityQueryWrapper);//查询权限具体信息for (RequestPermissionEntity requestPermissionEntity : requestPermissionEntities) {PermissionEntity permissionEntity = permissionMapper.selectById(requestPermissionEntity.getPermissionId());list.add(permissionEntity);}}return list;}
}

3、访问决策管理器AccessDecisionManager

package com.example.demo.service;import com.example.demo.common.PermissionException;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;import java.util.Collection;
import java.util.Iterator;/*** @Author: ylh* @Description: 访问决策管理器* @Date Create in 2019/9/3 20:38*/
@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {@Overridepublic void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {Iterator<ConfigAttribute> iterator = collection.iterator();while (iterator.hasNext()) {ConfigAttribute ca = iterator.next();//当前请求需要的权限String needRole = ca.getAttribute();//当前用户所具有的权限Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();for (GrantedAuthority authority : authorities) {if (authority.getAuthority().equals(needRole)) {return;}}}//权限不足抛出异常,会被AccessDeniedHandler拦截throw new AccessDeniedException("权限不足!");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}

4、在WebSecurityConfig中声明

http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setAccessDecisionManager(accessDecisionManager);//决策管理器o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源return o;}}).

5、重写AccessDeniedHandler拦截器,变成我们自定义返回的内容

package com.example.demo.config;import com.alibaba.fastjson.JSON;
import com.example.demo.common.JsonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Service;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @Author: ylh*/
@Service
public class CustomAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException {response.setContentType("application/json;charset=UTF-8");JsonResult jsonResult=new JsonResult<>();jsonResult.setErrorCode(403);jsonResult.setErrorMsg("权限不足");response.setContentType("application/json");response.setStatus(HttpServletResponse.SC_OK);String s = JSON.toJSONString(jsonResult);response.getWriter().write(s);}
}

使用user2进行访问,提示权限不足

SpringSecurity+JWT实现token登录

前面是将登录过后的信息存储在cookie中
下面将演示,结合JWT实现token的登录
引入两个依赖

       <!-- jwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency>

创建一个工具类

package com.example.demo.utils;import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.Date;/*** @ClassName JwtUtils* @Author ylh* @Date 2023/2/20 9:14* @Description*/@Data
@Component
@ConfigurationProperties(prefix = "ylh.jwt")
public class JwtUtils {private long expire;private String secret;private String header;// 生成JWTpublic String generateToken(String username) {Date nowDate = new Date();Date expireDate = new Date(nowDate.getTime() + 1000 * expire);return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(username).setIssuedAt(nowDate).setExpiration(expireDate)    // 7天过期.signWith(SignatureAlgorithm.HS512, secret).compact();}// 解析JWTpublic Claims getClaimsByToken(String jwt) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt).getBody();} catch (Exception e) {return null;}}// 判断JWT是否过期public boolean isTokenExpired(Claims claims) {return claims.getExpiration().before(new Date());}}

在配置文件中进行相关配置

ylh:jwt:header: Authorization #设置请求头名称expire: 604800 # 7天,s为单位secret: aiit

流程:当用户第一次登录成功后,返回一个token给前端,每次访问的时候,都要将token放到请求头中

1、修改登录成功的逻辑

在配置类的修改登录成功这一部分的逻辑,添加token的生成

注入jwt工具类

@Autowired
private JwtUtils jwtUtils;
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter filter = new CustomAuthenticationFilter();filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();//登录成功的返回JsonResult result = ResultTool.success();//生成token,返回给前端String jwt = jwtUtils.generateToken(authentication.getName());result.setData(jwt);out.write(new ObjectMapper().writeValueAsString(result));out.flush();out.close();}});

关闭cookie

//关闭cookie
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

2、添加未登录的异常捕获

package com.example.demo.handler;import com.alibaba.fastjson.JSON;
import com.example.demo.common.JsonResult;
import com.example.demo.common.ResultCode;
import com.example.demo.common.ResultTool;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @ClassName JwtAuthenticationEntryPoint* @Author ylh* @Date 2023/2/21 9:41* @Description  请求需要权限的接口时,判断登录状态*/@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {//自定义返回信息返回JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);response.setContentType("text/json;charset=utf-8");response.getWriter().write(JSON.toJSONString(result));}
}

在配置类中进行配置

//匿名用户访问无权限资源时的异常处理and().exceptionHandling()//AuthenticationException 是在用户认证的时候出现错误时抛出的异常。主要的子类如图:
.authenticationEntryPoint(jwtAuthenticationEntryPoint)

3、在判断用户名密码之前添加一个验证token的过滤器

JwtAuthenticationFilter

/*** @ClassName JwtAuthenticationFilter* @Author ylh* @Date 2023/2/20 10:34* @Description*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {@AutowiredJwtUtils jwtUtils;@Autowiredprivate UserService userService;public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}/*** 解析我们的jwt,根据解析的username查询数据库,进行权限配置*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String jwt = request.getHeader(jwtUtils.getHeader());// 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的// 没有jwt相当于匿名访问(比如我们的请求验证码接口),若有一些接口是需要权限的,则不能访问这些接口,在刚才的JwtAuthenticationEntryPoint 配置了if (StrUtil.isBlankOrUndefined(jwt)) {chain.doFilter(request, response);return;}Claims claim = jwtUtils.getClaimsByToken(jwt);if (claim == null) {throw new JwtException("token 异常");}if (jwtUtils.isTokenExpired(claim)) {throw new JwtException("token 已过期");}String username = claim.getSubject();//获取用户的权限等信息List<GrantedAuthority> grantedAuthorities  =userService.getUserPermissionByUsername(username);// 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);SecurityContextHolder.getContext().setAuthentication(token);chain.doFilter(request, response);}
}

注意:这里有个问题!!!全局异常无法捕获filter里的异常,所以我们直接抛出
JwtException是不可以的
解决办法:这里就使用请求转发给controller,返回给前端

    if (claim == null) {//请求转发给controllerrequest.getRequestDispatcher("/demo/401").forward(request,response);//            throw new JwtException("token 异常");}

在controlelr中创建一个异常返回接口

@GetMapping("/401")
public JsonResult error(){JsonResult jsonResult=new JsonResult(false);jsonResult.setErrorMsg("token异常");return jsonResult;
}

userService.getUserPermissionByUsername(username);//根据解析出来的用户名查询对应的权限

,构造User对象

public List<GrantedAuthority> getUserPermissionByUsername(String username) {//1、根据用户名查询用户信息QueryWrapper<UserEntity> queryWrapper=new QueryWrapper<>();queryWrapper.eq("account",username);UserEntity userEntity = userMapper.selectOne(queryWrapper);//2、根据用户名查询用户的权限信息//查询userIdInteger id = userEntity.getId();//查询权限idQueryWrapper<UserRoleRelation> queryWrapper1=new QueryWrapper<>();queryWrapper1.eq("user_id", id);UserRoleRelation userRoleRelation = userRoleMapper.selectOne(queryWrapper1);Integer roleId = userRoleRelation.getRoleId();//根据角色信息查询对应的权限信息//查询权限idQueryWrapper<RolePermissionRelation> rolePermissionRelationQueryWrapper = new QueryWrapper<>();rolePermissionRelationQueryWrapper.eq("role_id", roleId);List<RolePermissionRelation> rolePermissionRelations = rolePermissionMapper.selectList(rolePermissionRelationQueryWrapper);//权限列表List<GrantedAuthority> grantedAuthorities = new ArrayList<>();for (RolePermissionRelation rolePermissionRelation : rolePermissionRelations) {PermissionEntity permissionEntity = permissionMapper.selectById(rolePermissionRelation.getPermissionId());GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permissionEntity.getPermissionCode());grantedAuthorities.add(grantedAuthority);}return grantedAuthorities;}

在配置类中添加

        //添加token过滤器
http.addFilter(jwtAuthenticationFilter());

3、实现退出登录

在配置类中

//登出.and().logout().permitAll().//允许所有用户logoutSuccessHandler(logoutSuccessHandler);

创建CustomizeLogoutSuccessHandler,退出登录拦截器

@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {@Autowiredprivate JwtUtils jwtUtils;@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException, IOException {if (authentication != null) {new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);}//请求头置空httpServletResponse.setHeader(jwtUtils.getHeader(), "");JsonResult result = ResultTool.success();httpServletResponse.setContentType("text/json;charset=utf-8");httpServletResponse.getWriter().write(JSON.toJSONString(result));}
}

请求登录返回token

在登录过程中设置请求头

结合验证码实现登录

生成验证码

依赖坐标

<!--        Google的验证码工具类-->
<dependency><groupId>com.github.axet</groupId><artifactId>kaptcha</artifactId><version>0.0.9</version>
</dependency>

配置类

@Configuration
public class KaptchaConfig {@BeanDefaultKaptcha producer() {Properties properties = new Properties();properties.put("kaptcha.border", "no");properties.put("kaptcha.textproducer.font.color", "black");properties.put("kaptcha.textproducer.char.space", "4");properties.put("kaptcha.image.height", "40");properties.put("kaptcha.image.width", "120");properties.put("kaptcha.textproducer.font.size", "30");Config config = new Config(properties);DefaultKaptcha defaultKaptcha = new DefaultKaptcha();defaultKaptcha.setConfig(config);return defaultKaptcha;}}

编写一个生成验证码的接口

这里将生成的验证码key存储到redis中(我没有演示,自行设置)

返回给前端一个key

当前端登录时,需要账号,密码,验证码的code,验证码的key

    /*** 生成验证码接口* @return* @throws IOException*/@GetMapping("/captcha")public JsonResult Captcha() throws IOException {String key = UUID.randomUUID().toString();//缓存进入redis中String code = producer.createText();//生成的验证码内容BufferedImage image = producer.createImage(code);ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ImageIO.write(image, "jpg", outputStream);Base64Encoder encoder = new Base64Encoder();String str = "data:image/jpeg;base64,";String base64Img = str + encoder.encode(outputStream.toByteArray());/*** TODO 缓存验证码到redis中*/JsonResult jsonResult=new JsonResult(true);jsonResult.setData(code);jsonResult.setData(MapUtil.builder().put("userKey", key).put("captcherImg", base64Img).build());return  jsonResult;}

返回结果

{"success": true,"errorCode": 200,"errorMsg": "成功","data": {"captcherImg": "data:image/jpeg;base64,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","userKey": "33f02ffb-8d90-4b16-9e7f-f38daf7494df"}
}

验证码是一个base64编码的图片,前端需要进行解码

结合验证码登录

自定义一个异常,当验证码错误时,抛出这个异常

/*** @ClassName CaptchaException* @Author ylh* @Date 2023/2/20 14:53* @Description*/
public class CaptchaException extends AuthenticationException {public CaptchaException(String msg) {super(msg);}
}

在登录的时候,进行验证码校验

修改我们之前编写的CustomAuthenticationFilter

添加验证码校验


package com.example.demo.filter;import com.example.demo.exception.CaptchaException;
import com.example.demo.handler.CustomizeAuthenticationEntryPoint;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;/*** @ClassName CustomAuthenticationFilter* @Author ylh* @Date 2023/2/9 21:39* @Description  使用JSON格式登录*/
/*** @ClassName CustomAuthenticationFilter* @Author ylh* @Date 2023/2/9 21:39* @Description*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Autowiredprivate CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {ObjectMapper mapper = new ObjectMapper();UsernamePasswordAuthenticationToken authRequest = null;InputStream is = null ;try {is = request.getInputStream();} catch (IOException e) {throw new RuntimeException(e);}Map<String,String> authenticationBean = null;try {authenticationBean = mapper.readValue(is, Map.class);} catch (IOException e) {throw new RuntimeException(e);}//获取username的值String code = authenticationBean.get("code");//getUsernameParameter:username//获取passwordString userKey = authenticationBean.get("userKey");//getPasswordParameter:password//设置值/*** TODO 简单模拟一下*/if (code.equals(userKey)){throw new CaptchaException("验证码有误");}authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.get("username"), authenticationBean.get("password"));setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}else {return super.attemptAuthentication(request, response);}}
}

在配置类中
修改我们之前自定义的登录异常,判断是否是验证码异常

//登录失败filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();JsonResult result = ResultTool.fail();if (e instanceof CaptchaException) {result.setErrorMsg("验证码有误");}out.write(new ObjectMapper().writeValueAsString(result));out.flush();out.close();}});filter.setAuthenticationManager(authenticationManagerBean());return filter;}

这里直接模拟验证码是否正确(当userKey和code一样的时候,报错)

     /*** TODO 简单模拟一下*/if (code.equals(userKey)){throw new CaptchaException("验证码有误");}


在后期使用的时候,需要根据userKey查询redis中对应的code
进行比对

附上项目源码地址:
https://gitee.com/bai-xiaoyun/spring-security-entry-use-case.git

制作不易,觉得有用的话,留个赞再走吧

SpringSecurity前后端分离(包含token和验证码登录)相关推荐

  1. Django CSRF(什么是CSRF?)\Django前后端分离csrf token获取方式

    文章目录 Django CSRF 什么是CSRF? Django CSRF Django CSRF 中间件 Django,Ajax提交csrf_token处理 Django 设置 cookie 中的 ...

  2. SSM+JWT实现前后端分离的token验证

    SSM+JWT实现前后端分离的token验证 前言 什么是JWT 基于Token的验证流程 JWT的Token实现 后端部分 前端部分 测试 项目完整地址 前言 以前写的web项目都是没有前后端分离的 ...

  3. SpringSecurity - 前后端分离简单实战 - 环境准备

    SpringSecurity 学习指南大全 文章目录 SpringSecurity - 前后端分离简单实战 - 环境准备 最好的理解方法 环境准备 技术前提 项目架构 项目创建 项目配置 数据库配置 ...

  4. 前后端分离使用 Token 登录解决方案

    前后端分离使用 Token 登录解决方案:https://juejin.im/post/5b7ea1366fb9a01a0b319612 转载于:https://www.cnblogs.com/byd ...

  5. SpringBoot+SpringSecurity前后端分离+Jwt的权限认证(改造记录)

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/zzzgd_666/article/details/96444829 前言 一般来说,我们用Spr ...

  6. echarts前后端交互数据_SpringBoot2.0实战(26)整合SpringSecurity前后端分离JSON交互...

    在前端的文章中,我们实现了使用 SpringSecurity 实现登录鉴权,并使用数据库存储用户信息,实现登录鉴权 SpringBoot2.0实战(24)整合SpringSecurity之最简登录方法 ...

  7. SpringBoot+SpringSecurity前后端分离+Jwt的权限认证改造记录!

    大家好,我是宝哥! 前言 一般来说,我们用SpringSecurity默认的话是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity还自带login登录页,还 ...

  8. SpringBoot+SpringSecurity前后端分离+Jwt的权限认证

    前言 一般来说,我们用SpringSecurity默认的话是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity还自带login登录页,还让你配置登出页,错误 ...

  9. SpringSecurity前后端分离下对登录认证的管理

    本案例基于springboot2.0.4,只说对登录验证的管理,不涉及权限的管理.因为学习新东西一下子太多,很容易乱.        首先添加maven依赖,直接开启springboot自带的spri ...

最新文章

  1. 3.1.3 awk命令用法
  2. jsp页面遍历后台传递的对象
  3. vhdl变量赋初值_1.6 C++变量
  4. $arr[]=$v 把 $v 这个东西 放到 $arr[] 这个数组中
  5. 【TensorFlow-windows】学习笔记六——变分自编码器
  6. Mac OS使用技巧之十五:快捷方便的Mini Dock
  7. android按键事件响应函数,android 响应按键按下的onKeyDown()函数?
  8. HEVC之1 HM的使用
  9. SQL水平测试题目(一)
  10. Android字节码ASM插桩
  11. CreatePipe 函数
  12. angularJs-基础用法
  13. 基于Thinkphp的微信表白墙小程序源码
  14. 数字内容安全实验一:使用Matlab编程实现 RSA 加密算法
  15. LeetCode 825 friends-of-appropriate-ages
  16. 时间戳和时间字符串互转
  17. Unity—“合成大西瓜”小游戏笔记
  18. Antv | G2-Plot图表在vue项目中的使用
  19. 爬取 goubanjia 网站的免费 ip 免费 proxy JavaScript + Python + selenium
  20. 商标中R标和TM标的区别

热门文章

  1. 如何root安卓手机_电视盒子Root是什么意思,如何判断电视盒子是否需要Root?|电视盒子|机顶盒|安卓|操作系统...
  2. a370m显卡参数 a370m显卡什么水平 锐炫a370m怎么样
  3. 台式计算机中的pcu是什么,PCU是什么的缩写
  4. mongoose utc时间转换
  5. ButterKnife Zelezny使用
  6. SpringCloud Alibaba 详解
  7. python微信公众号爬虫_微信公众号推送信息爬取---python爬虫
  8. CentOS 7下MariaDB源码包编译安装
  9. 像素(px)到底绝对单位还是相对单位
  10. C 语言:#warning 指令