云易办springboot+vue后端

news2024/11/16 20:26:46

springboot+vue云易办后端项目完成

一.创建项目

创建父项目:yeb,

使用spring Initializr,完成创建之后删除无用文件夹,作为父项目

添加packaging

    <packaging>pom</packaging>

二.创建子模块:yeb-server

maven项目

1.pop.xml中添加父依赖

    <parent>
        <artifactId>yeb</artifactId>
        <groupId>com.lystudy</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

2.创建启动项:YebApplication.class

@SpringBootApplication
@MapperScan("com.lystudy.server.mapper")
public class YebApplication {
    public static void main(String[] args) {
        SpringApplication.run(YebApplication.class,args);
    }
}

3.配置必备依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
    </dependencies>

三:逆向工程:generator

创建模块yeb-genertor,与yeb-server一样,maven项目

1.配置依赖

    <dependencies>
        <!--web 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus 依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>
        <!--mybatis-plus 代码生成器依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>
        <!--freemarker 依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
        </dependency>
        <!--mysql 依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

2.逆向工程启动器

package com.lystudy.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.FileOutConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateConfig;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
 *
 * @author zhoubin
 * @since 1.0.0
 */

public class CodeGenerator {

	/**
	 * <p>
	 * 读取控制台内容
	 * </p>
	 */
	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();
		String projectPath = System.getProperty("user.dir");
		gc.setOutputDir(projectPath + "/yeb-generator/src/main/java");
		//作者
		gc.setAuthor("liyao");
		//打开输出目录
		gc.setOpen(false);
		//xml开启 BaseResultMap
		gc.setBaseResultMap(true);
		//xml 开启BaseColumnList
		gc.setBaseColumnList(true);
		// 实体属性 Swagger2 注解
		gc.setSwagger2(true);
		mpg.setGlobalConfig(gc);

		// 数据源配置
		DataSourceConfig dsc = new DataSourceConfig();
		dsc.setUrl("jdbc:mysql://localhost:3306/yeb?useUnicode=true&characterEncoding=UTF-8");
		dsc.setDriverName("com.mysql.jdbc.Driver");
		dsc.setUsername("root");
		dsc.setPassword("root");
		mpg.setDataSource(dsc);

		// 包配置
		PackageConfig pc = new PackageConfig();
		pc.setParent("com.lysduty")
				.setEntity("pojo")
				.setMapper("mapper")
				.setService("service")
				.setServiceImpl("service.impl")
				.setController("controller");
		mpg.setPackageInfo(pc);

		// 自定义配置
		InjectionConfig cfg = new InjectionConfig() {
			@Override
			public void initMap() {
				// to do nothing
			}
		};

		// 如果模板引擎是 freemarker
		String templatePath = "/templates/mapper.xml.ftl";
		// 如果模板引擎是 velocity
		// String templatePath = "/templates/mapper.xml.vm";

		// 自定义输出配置
		List<FileOutConfig> focList = new ArrayList<>();
		// 自定义配置会被优先输出
		focList.add(new FileOutConfig(templatePath) {
			@Override
			public String outputFile(TableInfo tableInfo) {
				// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
				return projectPath + "/yeb-generator/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper"
						+ StringPool.DOT_XML;
			}
		});
		cfg.setFileOutConfigList(focList);
		mpg.setCfg(cfg);

		// 配置模板
		TemplateConfig templateConfig = new TemplateConfig();

		templateConfig.setXml(null);
		mpg.setTemplate(templateConfig);

		// 策略配置
		StrategyConfig strategy = new StrategyConfig();
		//数据库表映射到实体的命名策略
		strategy.setNaming(NamingStrategy.underline_to_camel);
		//数据库表字段映射到实体的命名策略
		strategy.setColumnNaming(NamingStrategy.no_change);
		//lombok模型
		strategy.setEntityLombokModel(true);
		//生成 @RestController 控制器
		strategy.setRestControllerStyle(true);
		strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
		strategy.setControllerMappingHyphenStyle(true);
		//表前缀
		strategy.setTablePrefix("t_");
		mpg.setStrategy(strategy);
		mpg.setTemplateEngine(new FreemarkerTemplateEngine());
		mpg.execute();
	}

}

四.复制逆向工程生成的文件

将生成的controller、mapper、pojo、service以及test中mapper全部复制到yeb-server中,并且添加相关类,即为修改包的路径为此项目下,再回yeb-generator项目中删除原来生成的这些东西,已经无用。

五.学习springSerurity安全框架

1.概述

没有安全框架,需要手动梳理每个资源的访问空值,使用安全框架可以通过配置的方式实现对资源的访问限制

2.常用安全框架

1.springsecurity

基于spring的企业应用光学系统提供声明式的安全访问控制解决方案的安全框架,

2.apache shiro

3.springsecurity的demo
1.创建springinitializr,选择springserurity以及web
2.添加依赖
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
3.测试是否可用

创建controller,实现login方法

@Controller
public class LoginController {
    @RequestMapping("login")
    public String login(){
        System.out.println("执行登录方法");
        return "redirect:main.html";
    }
}

编写静态页面login.html,main.html

<form action="/login" method="post">
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <input type="submit" value="登录">
</form>

启动测试

若出现已经渲染过的登录页面,且用户名固定是user,密码固定是控制台出现的密码,则成功,否则失败

加密测试代码

    @Test
    void contextLoads() {
        PasswordEncoder pe = new BCryptPasswordEncoder();
        String encoude = pe.encode("123");
        System.out.println(encoude);
        System.out.println(pe.matches("123",encoude));
    }
4.使用springsecurity,必须有实体类

1.创建springsecurity配置类实体类

package com.lystudy.springsecuritydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
//springsecurity配置类
@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }
}
5.自定义配置类demo

1.创建SecurityConfig

//springsecurity配置类
@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }
}

2.创建service

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private PasswordEncoder pw;
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{

        /*
        1.查询数据库查看用户名是否存在,不存在抛出异常
        2.把查询出来的数据库中的密码(注册时已经加密过)进行解析,或者直接把密码放入构造方法

        * */
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("用户不存在");
        }
        String password = pw.encode("123");
		//这个user是security中的User,后面的list是权限类型以及个数,用逗号隔开
        return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

4.登录的成功与错误拦截

内置访问方法

  • permitAll():允许任何人访问
  • authenticated():必须认证才可以访问
  • denyAll():所有人都不可以访问
  • anonymous:与permitAll类似,但是有一个特殊区别
  • fullyAuthenticated():完全认证,必须一步步认证才可以访问
  • rememberMe():记住我,表示一定时限内不需要认证
1.在SecurityConfig中配置拦截方法

方法需要extends WebSecurityConfigurerAdapter

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //表单提交,
        http.formLogin()//此处可以设置用户名与密码别名:.usernameParameter("别名")或passwordParameter。跟表单内的同名即可
                .loginProcessingUrl("/login")//当发现login时是登录,必须和表单中提交的地址一样,去执行 UserDetailsServiceImpl
                .loginPage("/login.html")//自定义登录页面是login.html
                .successForwardUrl("/toMain")//成功跳转的页面,必须是post请求,("/main.html"是get请求)
                .failureForwardUrl("/toError");//登录失败的跳转页面,必须是post请求
        //授权认证,才可以使用
        http.authorizeRequests()
                .antMatchers("/error.html").permitAll()//失败页面也需要不认证
                .antMatchers("/login.html").permitAll()//login.html.此页面不认证
                .anyRequest().authenticated();//所有请求都被要求认证,必须登录之后才能被访问

        //关闭csrf防火墙防护
        http.csrf().disable();
    }
2.UserDetailServiceImpl中的验证

在此验证账号以及密码,本应该在数据库总验证,此为方便,自定义账号密码,即需要在implements UserDetailsService设置

    @Autowired
    private PasswordEncoder pw;
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        System.out.println("UserDetailsServiceImpl");
        /*
        1.查询数据库查看用户名是否存在,不存在抛出异常
        2.把查询出来的数据库中的密码(注册时已经加密过)进行解析,或者直接把密码放入构造方法

        * */
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("用户不存在");
        }
        String password = pw.encode("123");

        return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
3.跳转链接为前后端分离的链接

建立新包handle

创建的类方法MyAuthenticationSuccessHandler

实现AuthenticationSuccessHandler接口

url为SecurityConfig传过来的方法,下面的fail中的toError不可再使用,必须修改为上面的new的心类方法,跳转链接在里面定义

http.formLogin()
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                .successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))//登录成功处理器,不能和successForwardurl共存
                .failureForwardUrl("/toError");

handle中的新类的定义以下方法

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private String url;
    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

失败的方法

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler{
    private String url;
    public MyAuthenticationFailureHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

调用
http.formLogin().failureHandler(new MyAuthenticationFailureHandler("/error.html"));
4.antMatchers放行静态资源

利用anthorizeRequests方法中的antMatchers方法放行资源

 //授权认证,才可以使用
        http.authorizeRequests()
                .antMatchers("/error.html").permitAll()//失败页面也需要不认证
                .antMatchers("/login.html").permitAll()//login.html.此页面不认证
                .antMatchers("/images/**","/js/**","/css/**").permitAll()逗号相加即可添加放行的静态资源
                .anyRequest().authenticated();//所有请求都被要求认证,必须登录之后才能被访问

第二种方法

http.authorizeRequests().antMatchers("/**/*.png").permitAll()//所有静态文件以png格式的全部放行
5.regexMatchers放行静态资源

regexMatchers中使用正则表达式的方式放行

第一个点表示任意

+表示至少一个

[.]png表示png格式的文件

http.authorizeRequests().regexMatchers(".+[.]png").permitAll()//放行png格式的图片

必须是某种特殊方式的转送才能放行,此处例子是必须是post方式传输的才可以放行,底下定义的get方法就不可以放行

http.authorizeRequests().regexMatchers(HttpMethod.POST,"/demo").permitAll()//此时必须是post的demo才可以放行,前面的那个东西是指必须满足某种条件的demo方法
    @GetMapping("demo")
    @ResponseBody
    public String demo(){
        return "demo";
    }
6.修改访问路径:mvcMatchers方法

原本是localhost:8080/demo即可

修改为localhost:8080/lystudy/demo

首先在application.properties中添加配置

spring.mvc.servlet.path=/lystudy

拦截器中可以使用mvcMatchers方法

http.authorizeRequests() .mvcMatchers("/demo").servletPath("/lystudy").permitAll()

其中serveletPath方法是mvcmatchers特有的方法

同时上述也等效于

http.authorizeRequests().antMatchers("/lystudy/demo").permitAll()

访问方式:http://localhost:8080/lystudy/demo

7.角色权限的选择hasAuthority(单个)或者hasAnyAuthority(多个)

根据设置的角色权限:UserDetailsServiceImpl类中的loadUserByUsername方法

return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));

设置 哪些权限可以访问

http.authorizeRequests().antMatchers("main1.html").hasAuthority("admin","xxx","xxxx")
8.角色的判断:ROLE_abc

其中ROLE_abc中ROLE为固定写法,abc为角色名

return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
   

设置角色不拦截

.antMatchers("main1.html").hasAnyRole("abc")//abc为权限名,ROLE不可以写,否则运行报错
9.IP地址判断

打印IP地址的方法,我的本机是10.128.129.20

在MyAuthenticationSuccessHandler的onAuthenticationSuccess方法中打印System.out.println(request.getRemoteAddr());即可

比如127.0.0.1

设置方法:必须是http://127.0.0.1:8080/main1.html访问才可以

.antMatchers("/main1.html").hasIpAddress("127.0.0.1")

5.自定义异常处理方案(403)

我的电脑出现不是403,直接被禁止了,此处比较难受

1.创建处理方案MyAccessDeniedHandler
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setHeader("Content-Type","application/json;charset=utf-8");
        PrintWriter writer=response.getWriter();
        writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}");
        writer.flush();
        writer.close();
    }
}
2.调用myaccessdeniedhandler

在SecurityConfig中的configure中调用此方法

@Autowired
private MyAccessDeniedHandler myaccessdeniedhandler;
//异常处理
http.exceptionHandling()
     .accessDeniedHandler(myaccessdeniedhandler);

6.基于表达式的访问控制

1.access方法的使用:有很多种方法,凡是拥有的都可以像下面一样使用,具体可以百度

原来是这样的

http.authorizeRequests()
                .antMatchers("/error.html").permitAll()//失败页

现在是这样的

 http.authorizeRequests().antMatchers("/error.html").access("permitAll()")

两者的访问控制效果相同

再者

 .antMatchers("main1.html").access("hasRole('abc')").antMatchers("main1.html").hasRole("abc")
 访问控制效果相同
2.自定义访问控制
  1. 创建MyService

    public interface MyService {
        boolean hasPermission(HttpServletRequest request, Authentication authentication);
    }
    
  2. 创建MyServiceImpl

    @Service
    public class MyServiceImpl implements MyService{
        @Override
        public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
            //request是为了拿对应的主体,authentication是权限
            Object obj = authentication.getPrincipal();//获取主体,其实是User对象
            if(obj instanceof UserDetails){//因为user对象是User是userDetails下的User,故判断主体是否是同种类型
                UserDetails userdetails = (UserDetails) obj;//强转
                Collection<? extends GrantedAuthority> authorities = userdetails.getAuthorities();//拿到权限
                return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));//containsAll()是全部权限,此处用的是一个.
                //再判断权限是否是对应的url即上述的new方法,是则true,不是则false
    
            }
            return false;
        }
    }
    
  3. 替换原控制访问:在SecurityConfig中

原:http.authorizeRequests().anyRequest().authenticated();//所有请求都被要求认证,必须登录之后才能被访问

现:http.authorizeRequests().anyRequest().access(“@myServiceImpl.hasPermission(request,authentication)”);//采用自定义的访问控制来进行监控

结果:当前登录页面不拦截,但是登录过后转main.html拦截,因为没有权限,因此需要将main.html加入控制权限内,即UserDetailsServiceImpl中的loadUserByUsername方法内。而其他页面只要登录进入,则自然赋予了角色.

return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));

7.基于注解的访问控制

springsecurity中提供的一些访问控制注解,默认都是不可用的,需要通过@EnableGlobalMethodSecurity进行开启使用,如果设置的条件允许,正常运行,否则报500

这些注解可以写道Service接口或者方法上,也可以写道Controller以及Controller中的方法上,通常写在控制器方法上,控制URL是否被允许访问

1.@Secured

专门用于判断是否具有角色的。可以写在方法或类上,参数要以ROLE_开头

  1. 开启注解

在SpringsecuritydemoApplication中写上注解

@EnableGlobalMethodSecurity(securedEnabled = true),表示开启
  1. 在自定义方法上写注解,此处的ROLE_abc与原设置的角色名一定要一定并且要加ROLE_
 //页面跳转--成功
    @Secured("ROLE_abc")
    @RequestMapping("toMain")
    public String toMain(){
        return "redirect:main.html";
    }	
2.@PreAuthorize/@PostAuthorize

都是方法或者类级别的注解

@PreAuthorize表示方法或类在执行之前判断权限,大多是用这个,注解的参数与access()相同,方法名参数取值相同,都是权限表达式

@PostAuthorize执行结束后判断权限,很少用

@PreAuthorize("hasRole('abc')")    //表达式可以以ROLE_开头,配置类则不允许
@RequestMapping("toMain")
public String toMain(){
        return "redirect:main.html";
    }

8.RememberMe

记住我功能。用户只需要在登陆时添加remember-me复选框,取值为true,springsecurity自动把信息存储在数据源中,以后就可以不登录访问

1.添加依赖

底层需要用到jdbc,但是一般用的是mybatis,因此岛屿mybatis时还需要添加mysql驱动

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
2.application.properties数据库配置
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8
spring.datasource.username= root
spring.datasource.password= root
3.实现rememberme方法

需要自动注入userDetailsService和persistentTokenRepository

在springboot2.6版本以下,在这里注入出现循环注入的异常,需要用@Lazy来解除异常

http.rememberMe()
                .userDetailsService(userDetailsService)//登录逻辑
                .tokenRepository(persistentTokenRepository);//持久层对象
4.编写持久层对象

持久层对象是将需要记住的数据存储在数据库中,下次登录的时候就不需要登录直接进入即可

@Bean
public PersistentTokenRepository getPersistenToken(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);//设置数据源,因为需要链接数据库
        //jdbcTokenRepository.setCreateTableOnStartup(true);//自动简历表,第一次启动打开,第二次一定要注释掉
        return jdbcTokenRepository;
    }
5.修改记住时间等其他信息
http.rememberMe()
	.tokenValiditySeconds(30)//失效时间
	.rememberMeParameter("remember")//修改前台rememberme固定名称

9.springsecurity的使用(未实现)

1.引入依赖
<dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
2.创建相应读取数据的前端页面demo.html
<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5" >
<head>
    <meta charset="UTF-8">
    <title>demo</title>
</head>
<body>
登录账号:<span sec:authentication=“name”></span><br/>
登录账号:<span sec:authentication="principal.username"></span><br/>
凭证:<span sec:authentication="credentials"></span><br/>
权限和角色:<span sec:authentication="authenticas"></span><br/>
客户端地址:<span sec:authentication="details.remoteAddress"></span><br/>
sessionid:<span sec:authentication="details.sessionId"></span><br/>
判断当前用户是否拥有指定的权限。引号内的参数为权限的名称。
<span sec:authorize=“hasRole(‘role’)”></span>

<br/>
通过权限判断
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">选择</button>

</br>
通过角色判断
<button sec:authorize="hasRole('abc')">新增</button>

<br/>

<div sec:authorize="isAuthenticated()">
    <h2><span sec:authentication="name"></span>,您好 您的身份是
        <span sec:authentication="principal.authorities"></span>
    </h2>
</div>
</body>
</html>
3.controller创建跳转方法
@RequestMapping("demo")
    public String demo(){
        System.out.println("执行了demo");
        return "demo";
}

至此完成

4.实现退出功能(这个已经实现)

在main.html中设置退出链接:此为默认方法,一般使用此方法即可

<a href="/logout">退出</a><br/>

如果要修改方法名,或者指定退出后的跳转页面

可在SecurityConfig配置中设置

http.logout()
	//.logoutUrl("/logout")//指定退出页面
	.logoutSuccessUrl("/login.html");//退出登录跳转页面

10.SpringSecurity中的CSRF(跨站请求伪造)

刚开始学习springsecurity,配置类中一直存在这样一行代码

//关闭csrf防火墙防护
http.csrf().disable();

没有这段代码会导致用户无法被认证。

1.什么是CSRF

SCRF:跨站请求伪造,也被称为OneClick Attack或者Session Riding,通过伪造用户请求访问受信任站点的非法请求访问。

跨域:只要网络协议、IP地址、端口中任何一个不相同就是跨域请求

2.springsecurity中的CSRF

从4开始默认开启,默认拦截,为了抱枕不是其他第三方网站访问,要求访问时携带参数名未_csrf值未token的内容,若token与服务端的token匹配成功,则正常访问

3.实现方式
  1. templates中书写login.html方法,其中hidden为传csrf,name必须命名成_csrf
<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>showlogin</title>
</head>
<body>
<form action="/login" method="post">
    <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}">
    <span th:text="${_csrf.token}" th:if="${_csrf}"></span>
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    记住我:<input type="checkbox" name="remember-me" value="true">
    <input type="submit" value="登录">
</form>
</body>
</html>
  1. controller中书写页面跳转
   //页面跳转,关闭CSRF
    @RequestMapping("/showlogin")
    public String showlogin(){
        System.out.println("showlogin");
        return "login";
    }
  1. securityconfig中修改内容
 http.formLogin()..loginPage("/showlogin")//关闭csrf后的登录跳转
 http.authorizeRequests().antMatchers("/showlogin").permitAll()//关闭csrf后的登录跳转

六.Oauth2第三方认证技术

1.第三方认证技术主要解决认证标准的通用标准问题

SpringBoot整合SpingSecurityOAuth2,并实现授权码模式。

2.整合流程

image-20220411173158704

3.整合步骤

1.导入依赖
    <properties>
        <java.version>1.8</java.version>
        <!--SpringCloud版本-->
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    <dependencies>
        <!--oauth2依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <!--SpringSecurity依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <!--web组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--test组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

    <!--SpringCloud依赖-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
2.自定义登陆逻辑并配置WebSecurity相关的配置
@Service
public class UserService implements UserDetailsService {

    @Autowired
    PasswordEncoder passwordEncoder;

    /**
     * 自定义登陆方法
     *
     * @param username
     * @return org.springframework.security.core.userdetails.UserDetails
     * @author wanglufei
     * @date 2022/4/11 6:31 PM
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123456");
        return new User("admin", password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}
/**
 * @description: SpringSecurity配置类
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 自定义加密逻辑
     * @return org.springframework.security.crypto.password.PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义web相关的属性
     * @author wanglufei
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭CSRF
        http.csrf().disable()
                //授权
                .authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "/logout/**")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll();

    }
}

3.自定义User实现UserDetails接口
public class User implements UserDetails {
    private String username;
    private String password;
    private List<GrantedAuthority> authorities;//授权的


    public User(String username, String password, List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
4.授权服务器的配置。用来对资源拥有者的身份进行认证、对访问资源进行授权。客户端要想访问资源需要通过认证服务器由资源拥有者授权后可访问。
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    PasswordEncoder passwordEncoder;

    /**
     * 授权服务器的4个端点
     * * - `Authorize Endpoint` :授权端点,进行授权
     * * - `Token Endpoint` :令牌端点,进过授权拿到对应的Token
     * * - `Introspection Endpoint`:校验端点,校验Token的合法性
     * * - `Revocat ion Endpoint` :撤销端点,撒销授权
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //配置client Id
                .withClient("admin")
                //client-secret
                .secret(passwordEncoder.encode("112233"))
                //配置访问token的有效期
                .accessTokenValiditySeconds(3600)
                //配置重定向的跳转,用于授权成功之后的跳转
                .redirectUris("http:www.baidu.com")
                //作用域
                .scopes("all")
                //Grant_type  授权码模式
                .authorizedGrantTypes("authorization_code");
    }

}
5.资源服务器的配置。通常为用户,也可以是应用程序,既该资源的拥有者。
@Configuration
@EnableResourceServer
public class ResourcesServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //所有的访问都需要认证访问
        http.authorizeRequests().anyRequest().authenticated();
        //唯独user 可以访问 放行我们的资源
        http.requestMatchers().antMatchers("/user/**");
    }
}
6.controller层主要是通过Authentication得到主体,也就是我们当前的user
@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * 获取当前user
     *
     * @param authentication
     * @return java.lang.String
     * @author wanglufei
     * @date 2022/4/11 8:09 PM
     */
    @RequestMapping("/getCurrentUser")
    //authentication 认证
    public Object getCurrentUser(Authentication authentication) {
        return authentication.getPrincipal();
    }

}

4.实验结果

浏览器测试路径

http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_url=http://www.baidu.com&scope=all
1.测试

第一次运行,报错,显示SpringBoot和SpringCloud版本不兼容导致,所以要们降版本,要么升版本。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

成功获取

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入设置的账号密码:admin,123456

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

跳转成功获取授权码code值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用postman测试

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

获取到令牌后再重新创建一个测试路径,获取用户属性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

测试结束

5.使用密码模式获取用户资源

1.以上内容保留,部分稍作修改
2.修改区域
  1. 将AuthorizationServerConfig中的模式authorization_code修改为password,密码模式

  2. 此方法内添加密码模式的配置

    //密码模式,所需配置
        @Autowired
        private  AuthenticationManager authenticationManager;
        @Autowired
        private UserService userService;
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)
                    .userDetailsService(userService);
        }
    
  3. SecurityConfig添加Bean

//密码模式的bren
    @Bean
    public AuthenticationManager authenticationManager() throws Exception{
        return super.authenticationManager();
    }
3.测试
  1. postman中输入相关值外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.点击发送,获取相应的token值

3.使用得到的token获取相应的属性值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

七:JWT

1.jwt介绍

2.jwtd的demo:创建token

  1. 创建新项目jjwtdemo
  2. 引入依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

  1. 在test内编写测试代码
@SpringBootTest
public class JjwtdemoApplicationTests {

    @Test
    public void  testCreatToken(){
        //创建jwtBulider对象
        JwtBuilder jwtBuilder = Jwts.builder()
                .setId("8888")//声明的标识("jti:8888
                .setSubject("Rose")//主体:用户{sub:Rose}
                .setIssuedAt(new Date())//创建日期
                .signWith(SignatureAlgorithm.HS256,"xxxx");//盐是xxxx
        String token = jwtBuilder.compact();
        System.out.println(token);
        System.out.println("===============================");
        String[] split = token.split("\\.");
        System.out.println("第一个:"+Base64Codec.BASE64.decodeToString(split[0]));
        System.out.println("第二个:"+Base64Codec.BASE64.decodeToString(split[1]));
        System.out.println("第三个:"+Base64Codec.BASE64.decodeToString(split[2]));

    }

}
  1. 输出结果:因为盐的存在,每一次的输出结果都不一样
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY0OTc3NzAxMH0.TnEOaaukJ9q8G-x7ZZ7KdUQcRPKn27So8xWJUaaN1hw
===============================
第一个:{"alg":"HS256"}
第二个:{"jti":"8888","sub":"Rose","iat":164977701
第三个:Nqi��'ڼ�g��Q<���*<�bTi�u

3.jwt中demo解析token

  1. 使用上面生成的token:eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY0OTc3NzAxMH0.TnEOaaukJ9q8G-x7ZZ7KdUQcRPKn27So8xWJUaaN1hw
  2. 编写测试代码:盐依旧是”xxxx“
//解析token
    @Test
    public void testParseToken(){
        String token="eyJhbGciOiJIUzI1NiJ9" +
                ".eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY0OTc3NzAxMH0" +
                ".TnEOaaukJ9q8G-x7ZZ7KdUQcRPKn27So8xWJUaaN1hw";
        //解析token获取负载中声明的对象
        Claims claims = Jwts.parser()
                .setSigningKey("xxxx")
                .parseClaimsJws(token)
                .getBody();
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        System.out.println("issuedAt:"+claims.getIssuedAt());
    }
  1. 输出结果
id:8888
subject:Rose
issuedAt:Tue Apr 12 23:23:30 CST 2022

4.添加失效时间的token

1.创建新的token和添加时间在内

//创建token(失效时间)
@Test
public void  testCreatTokenHasExp(){
        //获取当前系统时间
    long now = System.currentTimeMillis();
    //过期时间:一分钟
    long exp = now + 60*1000;

    //创建jwtBulider对象
    JwtBuilder jwtBuilder = Jwts.builder()
            .setId("8888")//声明的标识("jti:8888
            .setSubject("Rose")//主体:用户{sub:Rose}
            .setIssuedAt(new Date())//创建日期
            .signWith(SignatureAlgorithm.HS256,"xxxx")//盐是xxxx
            .setExpiration(new Date(exp));//设置过期时间

    String token = jwtBuilder.compact();
    System.out.println(token);
    System.out.println("===============================");
    String[] split = token.split("\\.");
    System.out.println("第一个:"+Base64Codec.BASE64.decodeToString(split[0]));
    System.out.println("第二个:"+Base64Codec.BASE64.decodeToString(split[1]));
    System.out.println("第三个:"+Base64Codec.BASE64.decodeToString(split[2]));

}

2.创建测试解析token以及失效时间的代码

//解析token(失效时间)
    @Test
    public void testParseTokenHasExp(){
        String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY0OTc3ODE0NiwiZXhwIjoxNjQ5Nzc4MjA2fQ.Eh2CTwgLG1dwic6ZvolI7bsbf3Q6ih0Y2xiebIsPu-A";
        //解析token获取负载中声明的对象
        Claims claims = Jwts.parser()
                .setSigningKey("xxxx")
                .parseClaimsJws(token)
                .getBody();
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        System.out.println("issuedAt:"+claims.getIssuedAt());
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-DD HH-mm-ss");
        System.out.println("签发时间:"+simpleDateFormat.format(claims.getIssuedAt()));
        System.out.println("签发时间:"+simpleDateFormat.format(claims.getExpiration()));
        System.out.println("当前时间:"+simpleDateFormat.format(new Date()));


    }

3.解析结果

id:8888
subject:Rose
issuedAt:Tue Apr 12 23:42:26 CST 2022
签发时间:2022-04-102 23-42-26
签发时间:2022-04-102 23-43-26
当前时间:2022-04-102 23-43-05

5.自定义申明

1.自己添加相应的申明

//创建token(自定义token申明)
    @Test
    public void  testCreatTokenByClaims(){
        //创建jwtBulider对象
        JwtBuilder jwtBuilder = Jwts.builder()
                .setId("8888")//声明的标识("jti:8888
                .setSubject("Rose")//主体:用户{sub:Rose}
                .setIssuedAt(new Date())//创建日期
                .signWith(SignatureAlgorithm.HS256,"xxxx")//盐是xxxx
                //自定义声明,以下两种方式都可以,map中可以存放多个申明
                .claim("role","admin")
                .claim("logo","xxx.jpg");
                //.addClaims(map);

        String token = jwtBuilder.compact();
        System.out.println(token);
        System.out.println("===============================");
        String[] split = token.split("\\.");
        System.out.println("第一个:"+Base64Codec.BASE64.decodeToString(split[0]));
        System.out.println("第二个:"+Base64Codec.BASE64.decodeToString(split[1]));
        System.out.println("第三个:"+Base64Codec.BASE64.decodeToString(split[2]));

    }

2.解析所生成的token

//解析token(自定义申明)
    @Test
    public void testParseTokenByClaims(){
        String token="eyJhbGciOiJIUzI1NiJ9." +
                "eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY0OTc3ODYwMSwicm9sZSI6ImFkbWluIiwibG9nbyI6Inh4eC5qcGcifQ." +
                "2MSbp8cj7PC5OigPFWvMPLH3imS2tDwBY1wcySwW-is";
        //解析token获取负载中声明的对象
        Claims claims = Jwts.parser()
                .setSigningKey("xxxx")
                .parseClaimsJws(token)
                .getBody();
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        System.out.println("roles:"+claims.get("role"));
        System.out.println("logo:"+claims.get("logo"));
    }

3.测试结果

id:8888
subject:Rose
roles::admin
logo::xxx.jpg

6.psringsectrityOauth2使用整合JWT

1.配置jwt,authorizationserverconfig
   @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                //.tokenStore(tokenStore);redis存储token
                .tokenStore(tokenStore)//配置存储令牌策略
                .accessTokenConverter(jwtAccessTokenConverter);
    }
2.配置jwt,authorizationserverconfig
    //jwt
    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;//引入jwt
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
3.编写JwtTokenStoreConfig
@Configuration
public class JwtTokenStoreConfig {

    @Bean
    public TokenStore jwtTokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter accessTokenConverter=new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("test_key");//配置jwt的密钥
        return accessTokenConverter;
    }
}
4.测试

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

拿到accesstken去jwt官网解析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5配置jwt增强器enchancer
1.配置jwt增强器配置:JwtTokenEnhancer.class
//JWT内容增强器
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String,Object> info = new HashMap<>();
        info.put("enhance","enhance_info");
        ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info);
        return oAuth2AccessToken;
    }
}
2.Jwt配置中配置增强器相应的bean
//拓展jwt增强器相应的配置
    @Bean
    public JwtTokenEnhancer jwtTokenEnhancer(){
        return new JwtTokenEnhancer();
    }
3.授权服务器AuthorizationServerConfig中使用增强器
//jwt增强器的引入
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;

public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //以下是jwt增强器相应配置
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);//jwt转换,正常生成的是短的token,通过这一步变成长的
        enhancerChain.setTokenEnhancers(delegates);//放入enhancerChan中


        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                //.tokenStore(tokenStore);redis存储token
                .tokenStore(tokenStore)//配置存储令牌策略
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain);//放入增强器使用
    }
4.测试

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

拿出token去官网使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

拿到了enchance_info

6.解析JWT中的内容
1.引入依赖jjwt
<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
</dependency>
2.编写GetCurrentUser方法,获取token
//获取jwt的token
    public Object getCurrentUser(Authentication authentication, HttpServletRequest  request) {
        String head = request.getHeader("Authorization");//获取请求头
        String token = head.substring(head.indexOf("bearer")+7);//token是以bearer:后的数据是token的,
        System.out.println(token);
        return Jwts.parser()
                .setSigningKey("test_key".getBytes(StandardCharsets.UTF_8))//加入盐,并且注意是utf-8
                .parseClaimsJws(token)//放入token
                .getBody();//获取主体
    }
3.测试

http://localhost:8080/oauth/token

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

http://localhost:8080/user/getCurrentUser外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意下面要修改为No Auth,以及上面的内容是写在Header中的,且只写一个Authorization外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.添加刷新令牌

利用获取的刷新令牌,就可以使用刷新的令牌,然后当以获得的令牌过期时,获取新的令牌

1.添加配置

只需在AuthorizationServerConfig.Class中,密码模式里添加刷新令牌权限即可,即:

.authorizedGrantTypes("password","refresh_token");//密码模式
2.测试

http://localhost:8080/oauth/token可以获取到刷新令牌

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再复制一个此测试项目,再进行刷新令牌的获取

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意这里即可外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

八:yeb实现登录功能

1.添加springsecurity中pom依赖,并且在IAdminService中添加抽象方法

<!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
<!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
public interface IAdminService extends IService<Admin> {
    //登录之后返回token
    RespBean login(String username, String password, HttpServletRequest request);
    //根据用户名查询用户
    Admin getAdminByUserName(String username);
}

2.编写容器获取jwt中的token以及相关操作

创建类JwtTokenUtil

位置:package com.lystudy.server.config.security

@Component
public class JwtTokenUtil {

    private static final String CLAIM_KEY_USERNAME="sub";
    private static final String CLAIM_KEY_CREATED="created";
    @Value("${iwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    //根据用户信息生成token
    public String generateToken(UserDetails userDetails){
        Map<String,Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED,new Date());//获取创建时间
        return generateToken(claims);

    }

    //从token获取登录用户名
    public  String getUserNameFromToken(String token){
        String username;
        try{
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        }catch (Exception e){
            username = null;
        }
        return username;
    }
    //从token获取荷载
    public Claims getClaimsFromToken(String token){
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;

    }

    //判断token是否有效
    public  boolean validateToekn(String token,UserDetails userDetails){
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername())&& !isTokenExpired(token);
    }

    //判断token是否可以贝刷新
    public boolean canRefresh(String token){
        return !isTokenExpired(token);
    }
    //刷新token
    public String refreshToken(String token){
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }

    //判断token是否失效
    private boolean isTokenExpired(String token){
        Date expireDate = getExpiredDateFromToken(token);
        return expireDate.before(new Date());
    }
    //从token中获取失效时间
    private Date getExpiredDateFromToken(String token){
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }


    //根据荷载生成JWT token
    private  String generateToken(Map<String,Object> claims){
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512,secret)
                .compact();
    }

    //生成token失效时间
    private Date generateExpirationDate(){
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }


}

3.创建公共返回对象RespBean(pojo内)

当后台获取前台传来的请求头中的token结果无论正确与否,都根据相应的操作反向相应的状态码,从而进行下一步操作

//公共返回对象,
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
    private long code;
    private String message;
    private Object obj;
    //成功返回结果
    public static RespBean success(String message){
        return new RespBean(200,message,null);
    }
    public static RespBean success(String message,Object obj){
        return new RespBean(200,message,obj);
    }
    //失败返回
    public static RespBean error(String message){
        return new RespBean(500,message,null);
    }
    public static RespBean error(String message,Object obj){
        return new RespBean(500,message,obj);
    }

}

4.Admin继承UserDetails

实现security中的相关操作,继承后将相关类OverMethods,并将实现的方法中相应的属性修改为true,最后enable中的返回值修改为enabled

public class Admin implements Serializable, UserDetails {
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    public boolean isAccountNonLocked() {
        return true;
    }
    public boolean isCredentialsNonExpired() {
        return true;
    }

    public boolean isEnabled() {
        return enabled;
    }
}

5.创建一个新的AdminLoginParam类,用来存放账号密码

这个仅仅用来存放用户少量常用信息,就可以不用创建一个新的Admin的大实例,同时还可以在后续添加验证码等无需数据库的属性时,带来方便性。

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "AdminLogin的对象",description = "")
public class AdminLoginParam {
    @ApiModelProperty(value = "用户名",required = true)
    private String username;
    @ApiModelProperty(value = "密码",required = true)
    private String password;
}

6.创建LoginController,实现相关方法

@Api("LoginController")
@RestController
public class LoginController {
    @Autowired
    private IAdminService adminService;
    @ApiModelProperty(value = "登录之后返回token")
    public RespBean login(AdminLoginParam adminLoginParam, HttpServletRequest request){
        return adminService.login(adminLoginParam.getUsername(),adminLoginParam.getPassword(),request);
    }
    //获取当前用户信息
    @ApiOperation(value = "获取当前用户信息")
    public Admin getAdminInfo(Principal principal){
        if(null==principal){
            return null;
        }
        String username = principal.getName();
        Admin admin = adminService.getAdminByUserName(username);
        admin.setPassword(null);
        return admin;
    }
    //当传来的获得的token不是合法有效的,和前端定义好,直接调用logout,拿到后端的200状态码,然后前端拿到200码后,直接删除头部的token,使此用户无法再继续访问,即退出
    @ApiOperation(value = "退出登录")
    @PostMapping("/logout")
    public RespBean logout(){
        return RespBean.success("注销成功!");
    }
}

7.编写安全配置SecurityConfig

创建新类package com.lystudy.server.config.security.SecurityConfig

1.编写UserDetailsService获取用户信息
@Bean
    public UserDetailsService userDetailsService(){
        return username -> {
//重写此方法,之前默认是userDetailsService中的loaduserByusername方法
            Admin admin = adminService.getAdminByUserName(username);
            if(admin!=null){
                return admin;
            }
            return  null;
        };
    }
2.重写配置,使security在走登录逻辑的时候执行我们自己编写的userdetails
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

其中passwordEncoder()此处下面自己编写

@Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
3.security完整的配置
protected void configure(HttpSecurity http) throws Exception{
        //使用jwt,不需要csrf
        http.csrf()
                .disable()
                //不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/login","/logout")//允许登录访问
                .permitAll()
                //除了上面的,其他所有请求都需要认证
                .anyRequest()
                .authenticated()
                .and()
                //获取头部和缓存
                .headers()
                .cacheControl();
        //添加jwt,登录授权拦截器
        //Security第一层拦截器就是UsernamePasswordAuthenticationFilter
        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        http.exceptionHandling()
                //添加自定义未授权和未登录结果返回
                .accessDeniedHandler()//此处参数未添加,在后续步骤中
                .authenticationEntryPoint();
    }
@Bean
public JwtAuthencationTokenFilter jwtAuthencationTokenFilter(){
    return new JwtAuthencationTokenFilter();
}
4.前置拦截JwtAuthencationTokenFilter编写

package com.lystudy.server.config.security.JwtAuthencationTokenFilter

登录前置拦截相应信息

public class JwtAuthencationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;
    /**
     * Same contract as for {@code doFilter}, but guaranteed to be
     * just invoked once per request within a single request thread.
     * See {@link #shouldNotFilterAsyncDispatch()} for details.
     * <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the
     * default ServletRequest and ServletResponse ones.
     *
     * @param request
     * @param response
     * @param filterChain
     */
    @Override
    //前置拦截
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader(tokenHeader);//拿到带有token的header
        if(null!=authHeader && authHeader.startsWith(tokenHead)){//先判断token是否存在,存在就拿到token与用户名,去登录
            String authToken = authHeader.substring(tokenHead.length());
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            //token存在,但是未登录
            if(null!=username && null == SecurityContextHolder.getContext().getAuthentication()){
                //登录,使用的重写的登录方法
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                //登录完后,去验证token是否有效,重新设置到用户对象里去
                if(jwtTokenUtil.validateToekn(tokenHead,userDetails)){
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken
                            (userDetails,null,userDetails.getAuthorities());
                    authenticationToken.setDetails((new WebAuthenticationDetailsSource()).buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
        filterChain.doFilter(request,response);
    }
}

8.swagger接口文档

1.准备pom依赖
<!--        swaager依赖-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
<!--        swagger第三方依赖-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>
2.写SwaggerConfig相关配置

在package com.lystudy.server.config

@Configuration
@EnableSwagger2//开启swagger2
public class Swagger2Config {
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.lystudy.server.controller"))
                //.paths(PathSelectors.any())
                .build()
                //swagger添加全局授权,方法是下面的1  2  3  4
                .securityContexts(securityContexts())
                .securitySchemes(securitySchems());
    }
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("云E办接口文档")
                .description("云E办接口文档")
                .contact(new Contact("lystudy","http:localhost:8081/doc.html","lystudy@1670596206@qq.com"))
                .version("1.0")
                .build();
    }
    //1
    private List<ApiKey> securitySchems(){
        //设置请求头信息
        List<ApiKey> result = new ArrayList<>();
        //第一个参数是apikey的值,第二个是自己的要准备的key的值,第三个就是header
        ApiKey apiKey = new ApiKey("Authorization","Authorization", "Header");
        result.add(apiKey);
        return result;
    }
    //2
    private List<SecurityContext> securityContexts(){
        List<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/hello.*"));
        return result;
    }
    //3
    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaulAuth())
                .forPaths(PathSelectors.regex(pathRegex))
                .build();
    }
    //4
    private List<SecurityReference> defaulAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global","accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("Authorization",authorizationScopes));
        return result;
    }

}

3.编写测试类Hello.controller
@RestController
@Controller
@Api(tags ="hello")
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello,这里是hello Controller";
    }
}

4.加入放行路径且不走拦截链

在securityConfig中

//放行一些请求,使其不走拦截链
@Override
public void configure(WebSecurity web) throws Exception {
    //放行路径
    web.ignoring().antMatchers(
            "/login",
            "/logout",
            "/css/**",
            "/js/**",
            "/index/html",
            "favicon/ico",
            "/doc.html",
            "/webjars/**",
            "/swager-resources/**",
            "/v2/api-docs/**",
            "/captcha",
            "/ws/**"
    );
}

这样就可以进入API文档了

  • 若进不去则需要把拦截路径中的拦截全部关闭才可以进去
  • 代码如下
    protected void configure(HttpSecurity http) throws Exception{
        //使用jwt,不需要csrf
        http.csrf()
                .disable()
                //不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//                .and()
//                .authorizeRequests()//请求授权
//                .antMatchers("/login","/logout")//允许登录访问
//                .permitAll()
                //下面两个是指除了上面的,其他所有请求都需要认证,暂时关闭,因为API文档需要用,开启了则文档是空白
//                .anyRequest()
//                .authenticated()
                .and()
                //获取头部和缓存
                .headers()
                .cacheControl();
5.给swagger添加全局authorization使其拥有登录之后的访问功能

在createRestApi中添加方法

.securityContexts(securityContexts())
.securitySchemes(securitySchems());

此处在二中书写相关swagger配置已经实现,后续的1234均为此处

6.测试
1.登录进入API文档:http://localhost:8081/doc.html

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.进入LoginController进行登录,输入账号密码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.登录成功则返回下面的值
{
  "code": 200,
  "message": "adminserviceImpl恭喜你登录成功",
  "obj": {
    "tokenHead": "{jwt.tokenHead}",
    "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2NTAxMDkyNzU2NjAsImV4cCI6MTY1MDcxNDA3NX0.2qdKaIP3VyOoLWhObbqTRZyXJWLnaLVjd6cnUVyX1m7AlXEASOCqyMvrvxJIuSi54Sx0aZK-bsG4zgZ1TMW4lQ"
  }
}
4.进入获取用户信息,调试,发送,则可以获取当前用户信息
{
  "id": 1,
  "name": "系统管理员",
  "phone": "13812361398",
  "telephone": "71937538",
  "address": "香港特别行政区强县长寿柳州路p座123",
  "enabled": true,
  "username": "admin",
  "password": null,
  "userFace": "http://192.168.10.100:8888/group1/M00/00/00/wKgKZF6oHzuAXnw9AABaLsrkrQQ148.jpg",
  "remark": null,
  "authorities": null,
  "accountNonLocked": true,
  "credentialsNonExpired": true,
  "accountNonExpired": true
}
5.进入hello进行调试则可以获取以下调试结果
hello,这里是hello Controller

9.生成验证码功能(谷歌)

1.添加pom依赖
		<!-- google kaptcha依赖 -->
		<dependency>
			<groupId>com.github.axet</groupId>
			<artifactId>kaptcha</artifactId>
			<version>0.0.9</version>
		</dependency>
2.编写配置CaptchaConfig

在config包下,这里面很多内容可以根据需要进行修改

@Configuration
public class CaptchaConfig {

    @Bean
    public DefaultKaptcha defaultKaptcha(){
        //验证码生成器
        DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
        //配置
        Properties properties = new Properties();
        //是否有边框
        properties.setProperty("kaptcha.border", "yes");
        //设置边框颜色
        properties.setProperty("kaptcha.border.color", "105,179,90");
        //边框粗细度,默认为1
        // properties.setProperty("kaptcha.border.thickness","1");
        //验证码
        properties.setProperty("kaptcha.session.key","code");
        //验证码文本字符颜色 默认为黑色
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        //设置字体样式
        properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        //字体大小,默认40
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        //验证码文本字符内容范围 默认为abced2345678gfynmnpwx
        // properties.setProperty("kaptcha.textproducer.char.string", "");
        //字符长度,默认为5
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        //字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space", "4");
        //验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width", "100");
        //验证码图片高度 默认为40
        properties.setProperty("kaptcha.image.height", "40");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

}
3.编写CaptchaController实现类

只有生成校验码的部分是需要根据需要来写的,上面的一些设置固定的

注意:GetMapping中的prodeuces是在API文档内生成的验证码乱码时,进行添加,可以消除乱码

@RestController
public class CaptchaController {
    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @ApiOperation(value = "验证码")
    @GetMapping(value = "/captcha", produces = "image/jpeg")//后面的prodeuces是防止在接口文档里乱码,从而无法查看
    public void captcha(HttpServletRequest request, HttpServletResponse response) {
        // 定义response输出类型为image/jpeg类型
        response.setDateHeader("Expires", 0);
        // Set standard HTTP/1.1 no-cache headers.
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        // Set standard HTTP/1.0 no-cache header.
        response.setHeader("Pragma", "no-cache");
        // return a jpeg
        response.setContentType("image/jpeg");

//        以下才是业务逻辑,上述是固定内容
        //-------------------生成验证码 begin --------------------------
        //获取验证码文本内容
        String text = defaultKaptcha.createText();
        System.out.println("验证码内容:" + text);
        //将验证码文本内容放入session
        request.getSession().setAttribute("captcha", text);
        //根据文本验证码内容创建图形验证码
        BufferedImage image = defaultKaptcha.createImage(text);
        //利用流的形式将图片传输出去
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            //输出流输出图片,格式为jpg
            ImageIO.write(image, "jpg", outputStream);
            outputStream.flush();//刷出去
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭流,要先判断是否未空
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //-------------------生成验证码 end --------------------------
    }
}

九:Redis

1.安装redis,与another redis desktop manager

2.相关配置命令操作

1.相关配置命令

调用方法: congih bind

结果:bind
127.0.0.1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.redis数据类型(五种)

Stringhashlist、set、zset(sortet set:有序集合)

操作方式:set/get、hmset/hget、lpush/lrange、sadd/smenbers、zadd/zrangebyscore

1.String:一个键最大存储512MB

实例:

redis 127.0.0.1:6379> SET runoob "菜鸟教程"
OK
redis 127.0.0.1:6379> GET runoob
"菜鸟教程"
2.Hash:每个hash可以存储2^32-1个键值对(40多亿)

是一个键值对集合,即string类型的field和balue的映射表

实例:

redis 127.0.0.1:6379> HMSET runoob field1 "Hello" field2 "World"
"OK"
redis 127.0.0.1:6379> HGET runoob field1
"Hello"
redis 127.0.0.1:6379> HGET runoob field2
"World"
3.List:简单的字符串列表,最多可存储 2^32 - 1 元素

按照插入顺序排序,你可以添加一个元素到列表的头部或者尾部

默认先存后取,即栈式结构

实例:

redis 127.0.0.1:6379> lpush runoob redis
(integer) 1
redis 127.0.0.1:6379> lpush runoob mongodb
(integer) 2
redis 127.0.0.1:6379> lpush runoob rabbitmq
(integer) 3
redis 127.0.0.1:6379> lrange runoob 0 10
1) "rabbitmq"
2) "mongodb"
3) "redis"
4.Set:string类型的无序集合,最大的成员数为 2^32 - 1

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

每个元素只能被存取一次,故相同元素只能有一个

实例:sadd key member

redis 127.0.0.1:6379> sadd runoob redis
(integer) 1
redis 127.0.0.1:6379> sadd runoob mongodb
(integer) 1
redis 127.0.0.1:6379> sadd runoob rabbitmq
(integer) 1
redis 127.0.0.1:6379> sadd runoob rabbitmq
(integer) 0
redis 127.0.0.1:6379> smembers runoob

1) "redis"
2) "rabbitmq"
3) "mongodb"
5.zset(sorted set:有序集合)

zset也是string类型元素的集合,且不允许重复的成员

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

实例:zadd key score member

redis 127.0.0.1:6379> zadd runoob 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabbitmq
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabbitmq
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE runoob 0 1000
1) "mongodb"
2) "rabbitmq"
3) "redis"
6.使用场景

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.redis命令

1.连接命令
  • 本地连接,进入redis安装目录后
redis-cli
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> PING

PONG
  • ​ 远程连接
redis-cli -h host -p port -a password

$redis-cli -h 127.0.0.1 -p 6379 -a "mypass"
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> PING

PONG
2.基本命令(key)
序号命令及描述
1DEL key 该命令用于在 key 存在时删除 key。
2DUMP key 序列化给定 key ,并返回被序列化的值。
3EXISTS key 检查给定 key 是否存在。
4EXPIRE key seconds 为给定 key 设置过期时间,以秒计。
5EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
6PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。
7PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
8KEYS pattern 查找所有符合给定模式( pattern)的 key 。
9MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。
10PERSIST key 移除 key 的过期时间,key 将持久保持。
11PTTL key 以毫秒为单位返回 key 的剩余的过期时间。
12TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
13RANDOMKEY 从当前数据库中随机返回一个 key 。
14RENAME key newkey 修改 key 的名称
15RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。
16[SCAN cursor MATCH pattern] [COUNT count] 迭代数据库中的数据库键。
17TYPE key 返回 key 所储存的值的类型。
3.字符串命令(String)
序号命令及描述
1SET key value 设置指定 key 的值。
2GET key 获取指定 key 的值。
3GETRANGE key start end 返回 key 中字符串值的子字符
4GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
5GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
6[MGET key1 key2…] 获取所有(一个或多个)给定 key 的值。
7SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
8SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
9SETNX key value 只有在 key 不存在时设置 key 的值。
10SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
11STRLEN key 返回 key 所储存的字符串值的长度。
12[MSET key value key value …] 同时设置一个或多个 key-value 对。
13[MSETNX key value key value …] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
14PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
15INCR key 将 key 中储存的数字值增一。
16INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。
17INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。
18DECR key 将 key 中储存的数字值减一。
19DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。
20APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
4.Hash哈希相关命令
序号命令及描述
1[HDEL key field1 field2] 删除一个或多个哈希表字段
2HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
3HGET key field 获取存储在哈希表中指定字段的值。
4HGETALL key 获取在哈希表中指定 key 的所有字段和值
5HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。
6HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
7HKEYS key 获取所有哈希表中的字段
8HLEN key 获取哈希表中字段的数量
9[HMGET key field1 field2] 获取所有给定字段的值
10[HMSET key field1 value1 field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
11HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。
12HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。
13HVALS key 获取哈希表中所有值。
14[HSCAN key cursor MATCH pattern] [COUNT count] 迭代哈希表中的键值对。
5.List列表相关命令
序号命令及描述
1[BLPOP key1 key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
2[BRPOP key1 key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
3BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
4LINDEX key index 通过索引获取列表中的元素
5LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素
6LLEN key 获取列表长度
7LPOP key 移出并获取列表的第一个元素
8[LPUSH key value1 value2] 将一个或多个值插入到列表头部
9LPUSHX key value 将一个值插入到已存在的列表头部
10LRANGE key start stop 获取列表指定范围内的元素
11LREM key count value 移除列表元素
12LSET key index value 通过索引设置列表元素的值
13LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
14RPOP key 移除列表的最后一个元素,返回值为移除的元素。
15RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
16[RPUSH key value1 value2] 在列表中添加一个或多个值
17RPUSHX key value 为已存在的列表添加值
6.sorted set有序集合
序号命令及描述
1[ZADD key score1 member1 score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
2ZCARD key 获取有序集合的成员数
3ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
4ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
5[ZINTERSTORE destination numkeys key key …] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中
6ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量
7[ZRANGE key start stop WITHSCORES] 通过索引区间返回有序集合指定区间内的成员
8[ZRANGEBYLEX key min max LIMIT offset count] 通过字典区间返回有序集合的成员
9[ZRANGEBYSCORE key min max WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员
10ZRANK key member 返回有序集合中指定成员的索引
11[ZREM key member member …] 移除有序集合中的一个或多个成员
12ZREMRANGEBYLEX key min max 移除有序集合中给定的字典区间的所有成员
13ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员
14ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员
15[ZREVRANGE key start stop WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低
16[ZREVRANGEBYSCORE key max min WITHSCORES] 返回有序集中指定分数区间内的成员,分数从高到低排序
17ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
18ZSCORE key member 返回有序集中,成员的分数值
19[ZUNIONSTORE destination numkeys key key …] 计算给定的一个或多个有序集的并集,并存储在新的 key 中
20[ZSCAN key cursor MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值)

十:菜单功能

1.根据用户ID查询菜单列表

  1. 在MenuMapper中写接口List getMenusByAdminId(Integer id);
  2. 在IMenuService中对应此接口
  3. 在MenuServiceImplzhon给实现此接口
    @Autowired
    private MenuMapper menuMapper;

    //根据用户id查询菜单列表
    @Override
    public List<Menu> getMenusByAdminId() {
        return menuMapper.getMenusByAdminId(
                ((Admin) SecurityContextHolder.getContext().getAuthentication()
                        .getPrincipal()).getId());
    }
  1. 在MenuController实现方法

此处头部注解需要需改,因为表内不同

@RequestMapping("/system/cfg")
    @Autowired
    private IMenuService menuService;

    @ApiOperation(value = "通过用户id查询菜单列表")
    @GetMapping("/menu")
    public List<Menu> getMenusByAdminId(){
        return menuService.getMenusByAdminId();
    }
  1. 在MenuMapper.xml中编写sql语句
<select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT
            m1.*,
            m2.id AS id2,
            m2.url AS url2,
            m2.path AS path2,
            m2.component AS component2,
            m2.`name` AS name2,
            m2.iconCls AS iconCls2,
            m2.keepAlive AS keepAlive2,
            m2.requireAuth AS requireAuth2,
            m2.parentId AS parentId2,
            m2.enabled AS enabled2
        FROM
            t_menu m1,
            t_menu m2,
            t_admin_role ar,
            t_menu_role mr
        WHERE
            m1.id = m2.parentId
          AND m2.id = mr.mid
          AND mr.rid = ar.rid
          AND ar.adminId = #{id}
          AND m2.enabled = TRUE
        ORDER BY
            m2.id
    </select>
  1. 在这儿编写结果集
    <resultMap id="Menus" type="com.lystudy.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.lystudy.server.pojo.Menu">
            <id column="id2" property="id"/>
            <result column="url2" property="url"/>
            <result column="path2" property="path"/>
            <result column="component2" property="component"/>
            <result column="name2" property="name"/>
            <result column="iconCls2" property="iconCls"/>
            <result column="keepAlive2" property="keepAlive"/>
            <result column="requireAuth2" property="requireAuth"/>
            <result column="parentId2" property="parentId"/>
            <result column="enabled2" property="enabled"/>
        </collection>
    </resultMap>
  1. 测试结果:成功

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

key …]](https://www.runoob.com/redis/sorted-sets-zunionstore.html) 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
| 20 | [ZSCAN key cursor MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值) |

十:菜单功能

1.根据用户ID查询菜单列表

  1. 在MenuMapper中写接口List getMenusByAdminId(Integer id);
  2. 在IMenuService中对应此接口
  3. 在MenuServiceImplzhon给实现此接口
    @Autowired
    private MenuMapper menuMapper;

    //根据用户id查询菜单列表
    @Override
    public List<Menu> getMenusByAdminId() {
        return menuMapper.getMenusByAdminId(
                ((Admin) SecurityContextHolder.getContext().getAuthentication()
                        .getPrincipal()).getId());
    }
  1. 在MenuController实现方法

此处头部注解需要需改,因为表内不同

@RequestMapping("/system/cfg")
    @Autowired
    private IMenuService menuService;

    @ApiOperation(value = "通过用户id查询菜单列表")
    @GetMapping("/menu")
    public List<Menu> getMenusByAdminId(){
        return menuService.getMenusByAdminId();
    }
  1. 在MenuMapper.xml中编写sql语句
<select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT
            m1.*,
            m2.id AS id2,
            m2.url AS url2,
            m2.path AS path2,
            m2.component AS component2,
            m2.`name` AS name2,
            m2.iconCls AS iconCls2,
            m2.keepAlive AS keepAlive2,
            m2.requireAuth AS requireAuth2,
            m2.parentId AS parentId2,
            m2.enabled AS enabled2
        FROM
            t_menu m1,
            t_menu m2,
            t_admin_role ar,
            t_menu_role mr
        WHERE
            m1.id = m2.parentId
          AND m2.id = mr.mid
          AND mr.rid = ar.rid
          AND ar.adminId = #{id}
          AND m2.enabled = TRUE
        ORDER BY
            m2.id
    </select>
  1. 在这儿编写结果集
    <resultMap id="Menus" type="com.lystudy.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.lystudy.server.pojo.Menu">
            <id column="id2" property="id"/>
            <result column="url2" property="url"/>
            <result column="path2" property="path"/>
            <result column="component2" property="component"/>
            <result column="name2" property="name"/>
            <result column="iconCls2" property="iconCls"/>
            <result column="keepAlive2" property="keepAlive"/>
            <result column="requireAuth2" property="requireAuth"/>
            <result column="parentId2" property="parentId"/>
            <result column="enabled2" property="enabled"/>
        </collection>
    </resultMap>
  1. 测试结果:成功

[外链图片转存中…(img-kuKSdn7y-1716213606716)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1700422.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

这或许是国产游戏出海的最佳时机

《刺客信条&#xff1a;影》过于政治正确的选角激怒了全球玩家&#xff0c;而社交媒体上有玩家讨论育碧是否应该因为刻意加入DEI&#xff08;多元化&#xff09;而延期《刺客信条&#xff1a;影》时&#xff0c;没想到这得到了X拥有人埃隆马斯克的回复。马斯克表示“DEI&#x…

内网穿透实现公网访问自己搭建的Ollma架构的AI服务器

内网穿透实现公网访问自己搭建的Ollma架构的AI服务器 [2024年5月9号我发布一个博文关于搭建本地AI服务器的博文][https://blog.csdn.net/weixin_41905135/article/details/138588043?spm1001.2014.3001.5501]&#xff0c;今天我们内网穿透实现从公网访问我的本地AI服务器&…

【深度学习】最强算法之:人工神经网络(ANN)

人工神经网络ANN 1、引言2、人工神经网络(ANN)2.1 定义2.1.1 定义2.1.2 应用场景 2.2 核心原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;看新闻没&#xff1f; 小鱼&#xff1a;新闻天天看&#xff0c;啥事大惊小怪的。 小屌…

C++干货 --类和对象(二)

前言&#xff1a; 上文中&#xff0c;我们介绍了类这一重要知识点&#xff0c;包括为什么要有类、类的使用方法、封装、以及对象实例化。详情可以去看我的文章&#xff1a;写文章-CSDN创作中心C干货 --类和对象(一)-CSDN博客写文章-CSDN创作中心 这篇文章&#xff0c;我们简单…

全球点赞最高的人颜廷利:真正的人生目标是什么

在那个充满生机的2024年春天&#xff0c;记者有幸对中国第一起名大师的老师颜廷利教授进行了深入的访谈。带着对其人生哲学的强烈好奇&#xff0c;记者紧张而期待地提出了问题&#xff1a;“颜教授&#xff0c;您在漫长的人生旅途中最追求的是什么&#xff1f;” 宁夏银川、山东…

【模拟面试问答】深入解析力扣163题:缺失的区间(线性扫描与双指针法详解)

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

文盘Rust -- 生命周期问题引发的 static hashmap 锁

100编程书屋_孔夫子旧书网 2021年上半年,撸了个rust cli开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了。项目地址:https://github.com/jiashiwen/interactcli-rs。 春节以前看到axum已经0.4.x了,于是想看看能不能用rust做个服务端的框架。 春节…

C++的哈希 哈希表 哈希桶

目录 Unordered系列关联式容器 什么是哈希 哈希表 闭散列 载荷因子α 扩容 查找 删除 字符串哈希算法 最终代码 开散列 插入 查找 删除 最终代码 完整代码 Unordered系列关联式容器 C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0…

浅谈Docker容器的网络通信原理

文章目录 1、回顾容器概念2、容器网络3、容器与主机之间的网络连通4、交换机的虚拟实现---虚拟网桥&#xff08;Bridge&#xff09;5、Docker 守护进程daemon管理容器网络 1、回顾容器概念 我们知道容器允许我们在同一台宿主机&#xff08;电脑&#xff09;上运行多个服务&…

【蓝桥杯省赛真题44】python计算N+N的值 中小学青少年组蓝桥杯比赛 算法思维python编程省赛真题解析

目录 python计算NN的值 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python计算NN的值 第十四届蓝桥杯青少年组python省赛真题 一、题目要求…

VMware安装Ubuntu系统(超详细)

一.Ubuntu官网下载镜像 Ubuntu官网&#xff1a;Enterprise Open Source and Linux | Ubuntu 二.安装Ubuntu系统 选择文件->创建虚拟机新建虚拟机&#xff08;ControlN&#xff09;&#xff0c;这里直接选择典型即可 选择稍后安装系统 选择linux Ubuntu 64位 填写虚拟机名称…

PanTools v1.0.25 多网盘批量管理工具 批量管理、分享、转存、重命名、复制...

一款针对多个热门网盘的文件管理、批量分享、批量转存、批量复制、批量重命名、批量链接检测、跨账号移动文件、多账号文件搜索等&#xff0c;支持不同网盘的不同账号的资源文件操作。适用于网站站长、资源爱好者等&#xff0c;对于管理名下具有多个网盘多个账号具有实用的效果…

这方法真牛B!论文降重从81%直降1.9%

目录 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时二、获取途径三、论文从81&#xff05;降到1.9&#xff05;四、内容是别人的&#xff0c;话是自己的五、AI工具 --> 中文论文降重六、论文降重小技巧 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时 通过O…

Github 2024-05-27 开源项目周报Top15

根据Github Trendings的统计,本周(2024-05-27统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量TypeScript项目6Rust项目3Python项目3JavaScript项目3Java项目1C#项目1C++项目1Cuda项目1C项目1Lua项目1JavaScript算法与数据结构 创建周期:2…

读人工智能时代与人类未来笔记15_改变人类经验

1. 认识世界的方式 1.1. 信仰 1.2. 理性 1.2.1. 理性不仅革新了科学&#xff0c;也改变了我们的社会生活、艺术和信仰 1.2.2. 在其浸染之下&#xff0c;封建等级制度瓦解了&#xff0c;而民主&#xff0c;即理性的人应该自治的理念崛起了 1.3. 人工智能 1.3.1. 这种转变将…

关于我转生从零开始学C++这件事:升级Lv.25

❀❀❀ 文章由不准备秃的大伟原创 ❀❀❀ ♪♪♪ 若有转载&#xff0c;请联系博主哦~ ♪♪♪ ❤❤❤ 致力学好编程的宝藏博主&#xff0c;代码兴国&#xff01;❤❤❤ OK了老铁们&#xff0c;又是一个周末&#xff0c;大伟又来继续给大家更新我们的C的内容了。那么根据上一篇博…

Spring Boot 项目统一异常处理

在 Spring Boot 项目开发中&#xff0c;异常处理是一个非常重要的环节。良好的异常处理不仅能提高应用的健壮性&#xff0c;还能提升用户体验。本文将介绍如何在 Spring Boot 项目中实现统一异常处理。 统一异常处理有以下几个优点&#xff1a; 提高代码可维护性&#xff1a;…

每日AIGC最新进展(12):在舞蹈视频生成中将节拍与视觉相融合、Text-to-3D综述、通过内容感知形状调整进行 3D 形状增强

Diffusion Models专栏文章汇总&#xff1a;入门与实战 Dance Any Beat: Blending Beats with Visuals in Dance Video Generation https://DabFusion.github.io 本文提出了一种名为DabFusion的新型舞蹈视频生成模型&#xff0c;该模型能够根据给定的静态图像和音乐直接生成舞蹈…

优化FPGA SelectIO接口VREF生成电路

引言&#xff1a;FPGA设计中使用了各种PCB SelectIO™接口VREF生成电路。有时即使在以前的设计中已经成功的在电路板上设计了VREF生成电路&#xff0c;也会在VREF引脚上发现大量噪声&#xff08;200–400mV&#xff09;。大量VREF噪声的存在可能导致高性能SelectIO接口&#xf…

Jenkins部署成功后自动发通知到钉钉群

钉钉上如何配置 选择钉钉群&#xff0c;找到群设置-机器人-添加机器人 选择自定义 选择【添加】 选择【加签】&#xff0c;复制值&#xff0c;后续在jenkins里配置时会用到 复制Webhook地址&#xff0c;后面在jenkins里配置的时候要用到 Jenkins上如何配置 系统管理-插件管…