锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

SpringSecurity前后端分离

时间:2022-11-22 20:30:00 装饰连接器

SpringSecurity前后端分离(动态鉴定)

一、解释认证流程

1.原始认证流程

原始认证过程通常与Session一起使用,但是前后端分离后能使用Session了

SpringSecurity默认认证程序如下图所示(此图为B站UP主三更草堂讲SpringSecurity课程的图)

图片描述

DaoAuthenticationProvider继承AbstractUserDetailsAuthenticationProvider抽象类,而AbstractUserDetailsAuthenticationProvider抽象类又实现了AuthenticationProvider这个接口。

AuthenticationProvider接口和AuthenticationManager接口都有 authenticate() 这个方法

认证流程:

1.输入用户名和密码

2、UsernamePasswordAuthenticationFilter将用户名和密码包装成密码Authentication对象

然后再调用AuthenticationManager接口中的authenticate()方法进行认证,在AuthenticationManager接口实现类ProviderManager重写也被调用authenticate()认证方法。抽象类。AbstractUserDetailsAuthenticationProvider中重写了authenticate()方法

4、AbstractUserDetailsAuthenticationProviderauthenticate()该方法调用抽象法retrieveUser()方法

5、DaoAuthenticationProvider在重写方法retrieveUser()里调用了loadUserByUsername()方法

6、loadUserByUsername()方法会返回UserDetails对象认证成功返回上一层

2.前后端分离认证过程

在前后端分离后,我们要求在认证成功或失败时返回相应的状态代码,然后我们不再使用它Session经常使用认证管理jwt(JSON Web Token)认证引出了两种前后分离的写法

(图为B站UP主三更草堂讲SpringSecurity课程的图)

无论使用以下哪种写法,这里都需要UsernamePasswordAuthenticationFilter在前面加一个过滤器Token认证,如果Token认证成功意味着用户已登录;Token认证失败表明未登录或登录已过期。

2.1、继承UsernamePasswordAuthenticationFilter的写法

认证流程:

1.输入用户名和密码

2、MyUsernamePasswordAuthenticationFilter将用户名和密码包装成密码Authentication对象

然后再调用AuthenticationManager接口中的authenticate()认证方法,在AuthenticationManager接口实现类ProviderManager重写也被调用authenticate()认证方法。抽象类。AbstractUserDetailsAuthenticationProvider中重写了authenticate()方法

4、AbstractUserDetailsAuthenticationProviderauthenticate()该方法调用抽象法retrieveUser()方法

5、DaoAuthenticationProvider在重写方法retrieveUser()里调用了loadUserByUsername()方法,自定义AuthUserDetailsServiceImpl类实现UserDetailsService接口,重写loadUserByUsername()方法

6、在loadUserByUsername()在方法中,用户和角色将被查询并返回UserDetails对象

7、在继承WebSecurityConfigurerAdapter登录成功、失败处理器设置在类中,处理器定义返回状态码等信息

2.2.自定义写作

UsernamePasswordAuthenticationToken继承了AbstractAuthenticationToken抽象类,AbstractAuthenticationToken实现了抽象类Authentication接口

认证流程:

1.前端通过将用户名和密码发送到后端控制器,控制器调用业务层

2、Service层创建UsernamePasswordAuthenticationToken对象将用户名和密码包装成Authentication对象

3、然后调用AuthenticationManagerauthenticate()认证方法,抽象AbstractUserDetailsAuthenticationProvider中重写了authenticate()方法

4、AbstractUserDetailsAuthenticationProviderauthenticate()该方法调用抽象法retrieveUser()方法

5、DaoAuthenticationProvider在重写方法retrieveUser()里调用了loadUserByUsername()方法,自定义AuthUserDetailsServiceImpl类实现UserDetailsService接口,重写loadUserByUsername()方法

6、在loadUserByUsername()在方法中,用户和角色将被查询并返回UserDetails对象

2.3、区别

1、使用UsernamePasswordAuthenticationFilter自定义写法不需要使用登录成功和失败处理器,自定义写法可以自定义失败处理器(包括认证异常和授权异常)。登陆失败和没有权限)

2、使用UsernamePasswordAuthenticationFilter的写法对于扩展写法没那么友好,比如说添加手机验证码

二、数据库的设计

该示例是上面自定义的前后端分离的写法

这里使用的是Oracle数据库,这里没有权限的表,但是使用角色来判断也差不多

1、用户表

2、用户角色关系表

3、角色表

4、图片表

5、点赞表

三、初始配置

SpringBoot 版本是 2.6.0

1、项目结构

2、导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>

    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <optional>trueoptional>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-securityartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.securitygroupId>
        <artifactId>spring-security-testartifactId>
        <scope>testscope>
    dependency>

    
    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.4.1version>
    dependency>

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>

    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>1.2.74version>
    dependency>

    
    <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
        <version>5.5.6version>
    dependency>
    
    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-generatorartifactId>
        <version>3.4.1version>
    dependency>
    
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-lang3artifactId>
        <version>3.7version>
    dependency>
    
    <dependency>
        <groupId>org.apache.velocitygroupId>
        <artifactId>velocity-engine-coreartifactId>
        <version>2.2version>
    dependency>
    
    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger-uiartifactId>
        <version>2.7.0version>
    dependency>

    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger2artifactId>
        <version>2.7.0version>
    dependency>

    
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwtartifactId>
        <version>0.9.0version>
    dependency>

    
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <scope>runtimescope>
    dependency>

    
    <dependency>
        <groupId>com.oracle.database.jdbcgroupId>
        <artifactId>ojdbc8artifactId>
        <scope>runtimescope>
    dependency>

dependencies>

3、代码生成器

代码生成器这里最开始使用的是mysql 8.X版本的,读者需要自己修改一下数据库的名字,如果是mysql 5.X还需要修改一下驱动

后面才改用Oracle数据库,这里的代码就懒得改了

package com.guet.APPshareimage;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;

import java.util.Scanner;

/** * @Author LZDWTL * @Date 2021-12-15 17:09 * @ClassName CodeGenerator * @Description 代码生成器 */
public class CodeGenerator { 
        

    /** * 

* 读取控制台内容 *

*/
public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { // 创建代码生成器对象 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); gc.setOutputDir(scanner("请输入你的项目路径") + "/src/main/java"); //作者 gc.setAuthor("LZDWTL"); //生成之后是否打开资源管理器 gc.setOpen(false); //重新生成时是否覆盖文件 gc.setFileOverride(false); //%s 为占位符 //mp生成service层代码,默认接口名称第一个字母是有I gc.setServiceName("%sService"); //设置主键生成策略 自动增长 gc.setIdType(IdType.AUTO); //设置Date的类型 只使用 java.util.date 代替 gc.setDateType(DateType.ONLY_DATE); //开启实体属性 Swagger2 注解 gc.setSwagger2(true); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/shareimage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("shareimage"); dsc.setPassword("888888"); //使用mysql数据库 dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); //pc.setModuleName(scanner("请输入模块名")); pc.setParent("com.guet.APPshareimage"); pc.setController("controller"); pc.setService("service"); pc.setServiceImpl("service.impl"); pc.setMapper("mapper"); pc.setEntity("entity"); pc.setXml("mapper"); mpg.setPackageInfo(pc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); //设置哪些表需要自动生成 strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); //实体类名称驼峰命名 strategy.setNaming(NamingStrategy.underline_to_camel); //列名名称驼峰命名 strategy.setColumnNaming(NamingStrategy.underline_to_camel); //使用简化getter和setter strategy.setEntityLombokModel(true); //设置controller的api风格 使用RestController strategy.setRestControllerStyle(true); //驼峰转连字符 strategy.setControllerMappingHyphenStyle(true); //忽略表中生成实体类的前缀 //strategy.setTablePrefix("t_"); mpg.setStrategy(strategy); mpg.execute(); } }

运行代码生成器,复制路径输入,然后依次输入数据库中表的名字

D:\WorkSpace\JavaWorkSpce\ideal\APP-shareimage\APP-shareimage

t_user,t_picture,t_like,t_user_role,t_role

4、配置application.yml

根据自己的数据库和redis进行配置

server:
  port: 8080

spring:
  # 数据库配置
  datasource:
    driver-class-name: oracle.jdbc.driver.OracleDriver
    url: jdbc:oracle:thin:@120.77.80.135:1521:orcl
    username: XXXXXX
    password: XXXXXX
    # 连接池
    hikari:
      # 连接池名
      pool-name: DateHikariCP
      # 最小空闲连接数
      minimum-idle: 5
      # 空闲连接最大存活时间,默认600000(10分钟)
      idle-timeout: 180000
      # 最大连接数,默认10
      maximum-pool-size: 10
      # 从连接池返回的连接自动提交
      auto-commit: true
      # 连接最大存活时间,1800000(30分钟)
      max-lifetime: 1800000
      # 连接超时时间,默认30000(30秒)
      connection-timeout: 30000
      # 测试连接是否可用的查询语句
      #connection-test-query: SELECT 1 #这个是mysql的测试语句
      connection-test-query: SELECT * from dual  #这个是oracle的测试语句

  #redis配置
  redis:
    #服务器地址
    host: 120.77.80.135
    #端口
    port: 6379
    #redis密码
    password: XXXXXX
    #数据库,默认是0
    database: 0
    #超时时间
    timeout: 1209600000ms
    lettuce:
      pool:
        #最大链接数,默认8
        max-active: 8
        #最大连接阻塞等待时间,默认-1
        max-wait: 10000ms
        #最大空闲连接,默认8
        max-idle: 200
        #最小空闲连接,默认0
        min-idle: 5

mybatis-plus:
  mapper-locations: classpath:/mapper/*Mapper.xml
  type-aliases-package: com.guet.APPshareimage.entity

logging:
  level:
    com.guet.shareimage.mapper: debug

jwt:
  # JWT存储的请求头
  tokenHeader: Authorization
  # JWT 加解密使用的密钥
  secret: lzdwtl
  # JWT的超期限时间(1000*60*60*24*14)14天,即两周
  expiration: 1209600000
  # JWT 负载中拿到开头
  tokenHead: Bearer


role:
  roleid: 1

5、其他配置、工具类

5.1、SpringSecurity配置类

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
        

    @Autowired
    private MyOncePerRequestFilter myOncePerRequestFilter;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception { 
        
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception { 
        

        //1、关闭csrf,关闭Session
        http
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //2、设置不需要认证的URL
        http
                .authorizeRequests()
                //允许未登录的用户进行访问
                .antMatchers("/doLogin").anonymous()
                //其余url都要认证才能访问
                .anyRequest().authenticated();
    }
}

5.2、JSON格式返回配置类

public abstract class JSONAuthentication { 
        
    /** * 输出JSON * * @param request * @param response * @param obj * @throws IOException * @throws ServletException */
    protected void WriteJSON(HttpServletRequest request,
                             HttpServletResponse response,
                             Object obj) throws IOException, ServletException { 
        
        //这里很重要,否则页面获取不到正常的JSON数据集
        response.setContentType("application/json;charset=UTF-8");

        //跨域设置
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Method", "POST,GET");
        //输出JSON
        PrintWriter out = response.getWriter();
        out.write(JSON 

相关文章