云易办springboot+vue后端

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)]

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PyCharm基本配置内容

如何更换 Python 解释器 输入一段代码点击运行后&#xff0c;画面下方有一个路径如图中框中所示&#xff1a; 上面的路径为虚拟路径&#xff0c;可以改为我们自己设置的路径 点击设置&#xff0c;选择settings 选择Project&#xff1a;y002———》Python Interpreter&#…

Clickhouse 嵌套数据类型总结—— Clickhouse 基础篇(三)

文章目录 创建嵌套类型的表插入读取数据在嵌套类型上使用数组函数 在 clickhouse 中存储嵌套类型的关键字是 Nested, 只支持一级嵌套。数据结构类似于在数据结构类似于在表的单元格里面嵌套“一张表格”&#xff0c;如下图所示&#xff1a; 嵌套类型是列存储&#xff0c;本质…

OWASP十大API漏洞解析:如何抵御Bot攻击?

新型数字经济中&#xff0c;API是物联网设备、Web和移动应用以及业务合作伙伴流程的入口点。然而&#xff0c;API也是犯罪分子的前门&#xff0c;许多人依靠Bot来发动攻击。对于安全团队来说&#xff0c;保护API并缓解Bot攻击至关重要。那么Bot在API攻击中处于怎样的地位&#…

【JVM】一次JVM内存泄露分析处理

一次内存泄露分析 背景情况 编写了一个大数据基础组件的可用性监控程序&#xff0c;采用Bootstrap监测端口的方式&#xff0c;使得方法常驻&#xff08;main线程常驻&#xff09;&#xff0c;通过一个调度线程ScheduledThreadPoolExecutor&#xff0c;定时的调动监测任务。 …

OTFS系统建模、通信性能分析、信道估计、模糊函数【附MATLAB代码】

文献来源&#xff1a;​微信公众号&#xff1a;EW Frontier OTFS简介 OTFS信道估计 % Clear command window, workspace variables, and close all figures clc; clear all; close all; ​ % Define Eb values in dB EbdB -10:2:10; ​ % Convert Eb values from dB to lin…

微软提出“Copilot+ PCs”构想,强调本地AI处理;OpenAI暂停ChatGPT语音功能因声音相似争议

&#x1f989; AI新闻 &#x1f680; 微软提出“Copilot PCs”构想&#xff0c;强调本地AI处理 摘要&#xff1a;在微软 Build 开发者前瞻大会上&#xff0c;CEO 萨蒂亚・纳德拉介绍了“Copilot PCs”&#xff0c;一种新类 Windows PC&#xff0c;需配备神经处理单元&#xf…

视频技术在智慧营业厅中的应用:AI识别与智能化转型

一、方案背景 随着信息技术的快速发展&#xff0c;图像和视频分析技术已广泛应用于各行各业&#xff0c;特别是在营业厅场景中&#xff0c;该技术能够有效提升服务质量、优化客户体验&#xff0c;并提高安全保障水平。TSINGSEE青犀智慧营业厅视频管理方案旨在探讨视频监控和视…

你真的懂firewalld吗?不妨看看我的这篇文章

一、firewalld简介 firewalld防火墙是Linux系统上的一种动态防火墙管理工具&#xff0c;它是Red Hat公司开发的&#xff0c;并在许多Linux发行版中被采用。相对于传统的静态防火墙规则&#xff0c;firewalld使用动态的方式来管理防火墙规则&#xff0c;可以更加灵活地适应不同…

ld链接文件

文章目录 1. sections缩写2. 链接脚本2.1 MEMORY&#xff08;内存命令&#xff09;2.1.1 作用2.1.2 格式 2.2 SECTIONS&#xff08;段命令&#xff09;2.2.1 作用2.2.2 格式 2.3 特殊符号含义2.4 通配符2.5 Eg 1. sections缩写 2. 链接脚本 https://www.cnblogs.com/jianhua19…

mysql 01 linux 上安装mysql服务端

01.linux安装 MySQL的大部分安装包都包含了服务器程序和客户端程序&#xff0c;不过在Linux下使用RPM包时会有单独的服 务器RPM包和客户端RPM包&#xff0c;需要分别安装。 1.查看是否已经安装了MySQL rpm -qa | grep mysql如果什么都没有&#xff0c;就是还没有装过MySQL …

【设计模式】JAVA Design Patterns——Circuit Breaker(断路器模式)

&#x1f50d;目的 以这样一种方式处理昂贵的远程服务调用&#xff0c;即单个服务/组件的故障不会导致整个应用程序宕机&#xff0c;我们可以尽快重新连接到服务 &#x1f50d;解释 真实世界例子 想象一个 Web 应用程序&#xff0c;它同时具有用于获取数据的本地文件/图像和远程…

(1) 初识QT5

文章目录 Qt Quickdemo信号的命名方式 qml语言一个很重要的概念 qt 模块 Qt Quick Qt Quick是Qt5中⽤户界⾯技术的涵盖。Qt Quick⾃⾝包含了以下⼏种技术&#xff1a; QML-使⽤于⽤户界⾯的标识语⾔JavaScript-动态脚本语⾔Qt C具有⾼度可移植性的C库. 类似HTML语⾔&#xf…

生成式AI的GPU网络技术架构

生成式AI的GPU网络 引言&#xff1a;超大规模企业竞相部署拥有64K GPU的大型集群&#xff0c;以支撑各种生成式AI训练需求。尽管庞大Transformer模型与数据集需数千GPU&#xff0c;但实现GPU间任意非阻塞连接或显冗余。如何高效利用资源&#xff0c;成为业界关注焦点。 张量并…

泰达克TADHE uv胶水在粘接聚酰亚胺(Polyimide,PI)时具有一些优势,并在各行业中得到了广泛应用,尤其是在特定应用中

泰达克TADHE uv胶水在粘接聚酰亚胺&#xff08;Polyimide&#xff0c;PI&#xff09;时具有一些优势&#xff0c;并在各行业中得到了广泛应用&#xff0c;尤其是在特定应用中。以下是一些使用UV胶水粘接PI的优势&#xff1a; 1.快速固化&#xff1a; UV胶水通过紫外线照射进行固…

Java进阶学习笔记23——API概述

API&#xff1a; API&#xff08;Application Programming Interface&#xff09;应用程序编程接口 就是Java帮我们写好了一些程序&#xff1a;如类、方法等等&#xff0c;我们直接拿过来用就可以解决一些问题。 为什么要学别人写好的程序&#xff1f; 不要重复造轮子。开发…

回文链表(快慢指针解法之在推进过程中反转)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;抱怨深处黑暗&#xff0c;不如提灯前行…

系统开发与运行知识

系统开发与运行知识 导航 文章目录 系统开发与运行知识导航一、软件工程二、软件生命周期三、开发模型四、开发方法五、需求分析结构化分析 六、数据流图分层数据流图的画法设计注意事项 七、数据字典数据字典的内容 八、系统设计九、结构化设计常用工具十、面向对象十一、UML…

【Windows】本地磁盘挂载 Minio 桶

目录 1.软件安装安装winfsp支持安装rclone 2.新建rclone远程存储类型S3服务类型验证方式地区终端地址ACL服务端加密KMS 3.挂载存储盘 1.软件安装 安装winfsp支持 下载地址 或 下载地址2 文件为msi文件&#xff0c;下载后双击直接安装即可&#xff0c;可以选择安装路径 安装r…

接口响应断言-json

json认识JSONPath源码类学习/json串的解析拓展学习 目的&#xff1a;数据返回值校验测试 json认识 json是什么-是一种数据交换格式&#xff0c;举例平时看到的json图2&#xff0c;在使用中查看不方便&#xff0c;会有格式转化的平台&#xff0c;json格式的展示 JSON在线视图…

【好书推荐-第十八期】《 进化深度学习》

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公众号&#xff1a;洲与AI。 &#x1f388; 本文专栏&#xff1a;本文收录…