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开启 BaseResultMapgc.setBaseResultMap(true);//xml 开启BaseColumnListgc.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() {@Overridepublic void initMap() {// to do nothing}};// 如果模板引擎是 freemarkerString templatePath = "/templates/mapper.xml.ftl";// 如果模板引擎是 velocity// String templatePath = "/templates/mapper.xml.vm";// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();// 自定义配置会被优先输出focList.add(new FileOutConfig(templatePath) {@Overridepublic 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,密码固定是控制台出现的密码,则成功,否则失败
加密测试代码
@Testvoid 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 {@Beanpublic PasswordEncoder getPw(){return new BCryptPasswordEncoder();}
}
5.自定义配置类demo
1.创建SecurityConfig
//springsecurity配置类
@Configuration
public class SecurityConfig {@Beanpublic PasswordEncoder getPw(){return new BCryptPasswordEncoder();}
}
2.创建service
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate 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
@Overrideprotected 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设置
@Autowiredprivate 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;}@Overridepublic 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;}@Overridepublic 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")@ResponseBodypublic 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 {@Overridepublic 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.自定义访问控制
-
创建MyService
public interface MyService {boolean hasPermission(HttpServletRequest request, Authentication authentication); }
-
创建MyServiceImpl
@Service public class MyServiceImpl implements MyService{@Overridepublic 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;} }
-
替换原控制访问:在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_开头
- 开启注解
在SpringsecuritydemoApplication中写上注解
@EnableGlobalMethodSecurity(securedEnabled = true),表示开启
- 在自定义方法上写注解,此处的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.实现方式
- 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>
- controller中书写页面跳转
//页面跳转,关闭CSRF@RequestMapping("/showlogin")public String showlogin(){System.out.println("showlogin");return "login";}
- securityconfig中修改内容
http.formLogin()..loginPage("/showlogin")//关闭csrf后的登录跳转http.authorizeRequests().antMatchers("/showlogin").permitAll()//关闭csrf后的登录跳转
六.Oauth2第三方认证技术
1.第三方认证技术主要解决认证标准的通用标准问题
SpringBoot整合SpingSecurityOAuth2,并实现授权码模式。
2.整合流程
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 {@AutowiredPasswordEncoder passwordEncoder;/*** 自定义登陆方法** @param username* @return org.springframework.security.core.userdetails.UserDetails* @author wanglufei* @date 2022/4/11 6:31 PM*/@Overridepublic 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*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 自定义web相关的属性* @author wanglufei*/@Overrideprotected void configure(HttpSecurity http) throws Exception {//关闭CSRFhttp.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;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
4.授权服务器的配置。用来对资源拥有者的身份进行认证、对访问资源进行授权。客户端要想访问资源需要通过认证服务器由资源拥有者授权后可访问。
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@AutowiredPasswordEncoder passwordEncoder;/*** 授权服务器的4个端点* * - `Authorize Endpoint` :授权端点,进行授权* * - `Token Endpoint` :令牌端点,进过授权拿到对应的Token* * - `Introspection Endpoint`:校验端点,校验Token的合法性* * - `Revocat ion Endpoint` :撤销端点,撒销授权*/@Overridepublic 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 {@Overridepublic 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.修改区域
-
将AuthorizationServerConfig中的模式authorization_code修改为password,密码模式
-
此方法内添加密码模式的配置
//密码模式,所需配置@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate UserService userService;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager).userDetailsService(userService);}
-
SecurityConfig添加Bean
//密码模式的bren@Beanpublic AuthenticationManager authenticationManager() throws Exception{return super.authenticationManager();}
3.测试
- postman中输入相关值
2.点击发送,获取相应的token值
3.使用得到的token获取相应的属性值
七:JWT
1.jwt介绍
2.jwtd的demo:创建token
- 创建新项目jjwtdemo
- 引入依赖
<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>
- 在test内编写测试代码
@SpringBootTest
public class JjwtdemoApplicationTests {@Testpublic void testCreatToken(){//创建jwtBulider对象JwtBuilder jwtBuilder = Jwts.builder().setId("8888")//声明的标识("jti:8888.setSubject("Rose")//主体:用户{sub:Rose}.setIssuedAt(new Date())//创建日期.signWith(SignatureAlgorithm.HS256,"xxxx");//盐是xxxxString 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]));}}
- 输出结果:因为盐的存在,每一次的输出结果都不一样
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY0OTc3NzAxMH0.TnEOaaukJ9q8G-x7ZZ7KdUQcRPKn27So8xWJUaaN1hw
===============================
第一个:{"alg":"HS256"}
第二个:{"jti":"8888","sub":"Rose","iat":164977701
第三个:Nqi��'ڼ�g��Q<���*<�bTi�u
3.jwt中demo解析token
- 使用上面生成的token:eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY0OTc3NzAxMH0.TnEOaaukJ9q8G-x7ZZ7KdUQcRPKn27So8xWJUaaN1hw
- 编写测试代码:盐依旧是”xxxx“
//解析token@Testpublic 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());}
- 输出结果
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(失效时间)@Testpublic 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申明)@Testpublic 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(自定义申明)@Testpublic 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
@Overridepublic 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@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;
3.编写JwtTokenStoreConfig
@Configuration
public class JwtTokenStoreConfig {@Beanpublic TokenStore jwtTokenStore(){return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic 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 {@Overridepublic 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增强器相应的配置@Beanpublic 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的tokenpublic 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> {//登录之后返回tokenRespBean 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;//根据用户信息生成tokenpublic 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);}//刷新tokenpublic 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 tokenprivate 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 {@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic 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 {@Autowiredprivate 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获取用户信息
@Beanpublic 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()此处下面自己编写
@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
3.security完整的配置
protected void configure(HttpSecurity http) throws Exception{//使用jwt,不需要csrfhttp.csrf().disable()//不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/login","/logout")//允许登录访问.permitAll()//除了上面的,其他所有请求都需要认证.anyRequest().authenticated().and()//获取头部和缓存.headers().cacheControl();//添加jwt,登录授权拦截器//Security第一层拦截器就是UsernamePasswordAuthenticationFilterhttp.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;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate 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的headerif(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 {@Beanpublic 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();}//1private List<ApiKey> securitySchems(){//设置请求头信息List<ApiKey> result = new ArrayList<>();//第一个参数是apikey的值,第二个是自己的要准备的key的值,第三个就是headerApiKey apiKey = new ApiKey("Authorization","Authorization", "Header");result.add(apiKey);return result;}//2private List<SecurityContext> securityContexts(){List<SecurityContext> result = new ArrayList<>();result.add(getContextByPath("/hello.*"));return result;}//3private SecurityContext getContextByPath(String pathRegex) {return SecurityContext.builder().securityReferences(defaulAuth()).forPaths(PathSelectors.regex(pathRegex)).build();}//4private 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,不需要csrfhttp.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 {@Beanpublic 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", "宋体,楷体,微软雅黑");//字体大小,默认40properties.setProperty("kaptcha.textproducer.font.size", "30");//验证码文本字符内容范围 默认为abced2345678gfynmnpwx// properties.setProperty("kaptcha.textproducer.char.string", "");//字符长度,默认为5properties.setProperty("kaptcha.textproducer.char.length", "4");//字符间距 默认为2properties.setProperty("kaptcha.textproducer.char.space", "4");//验证码图片宽度 默认为200properties.setProperty("kaptcha.image.width", "100");//验证码图片高度 默认为40properties.setProperty("kaptcha.image.height", "40");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}}
3.编写CaptchaController实现类
只有生成校验码的部分是需要根据需要来写的,上面的一些设置固定的
注意:GetMapping中的prodeuces是在API文档内生成的验证码乱码时,进行添加,可以消除乱码
@RestController
public class CaptchaController {@Autowiredprivate 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 jpegresponse.setContentType("image/jpeg");// 以下才是业务逻辑,上述是固定内容//-------------------生成验证码 begin --------------------------//获取验证码文本内容String text = defaultKaptcha.createText();System.out.println("验证码内容:" + text);//将验证码文本内容放入sessionrequest.getSession().setAttribute("captcha", text);//根据文本验证码内容创建图形验证码BufferedImage image = defaultKaptcha.createImage(text);//利用流的形式将图片传输出去ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();//输出流输出图片,格式为jpgImageIO.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数据类型(五种)
String、hash、list、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 runoob1) "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> PINGPONG
- 远程连接
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> PINGPONG
2.基本命令(key)
序号 | 命令及描述 |
---|---|
1 | DEL key 该命令用于在 key 存在时删除 key。 |
2 | DUMP key 序列化给定 key ,并返回被序列化的值。 |
3 | EXISTS key 检查给定 key 是否存在。 |
4 | EXPIRE key seconds 为给定 key 设置过期时间,以秒计。 |
5 | EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
6 | PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。 |
7 | PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
8 | KEYS pattern 查找所有符合给定模式( pattern)的 key 。 |
9 | MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。 |
10 | PERSIST key 移除 key 的过期时间,key 将持久保持。 |
11 | PTTL key 以毫秒为单位返回 key 的剩余的过期时间。 |
12 | TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
13 | RANDOMKEY 从当前数据库中随机返回一个 key 。 |
14 | RENAME key newkey 修改 key 的名称 |
15 | RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
16 | [SCAN cursor MATCH pattern] [COUNT count] 迭代数据库中的数据库键。 |
17 | TYPE key 返回 key 所储存的值的类型。 |
3.字符串命令(String)
序号 | 命令及描述 |
---|---|
1 | SET key value 设置指定 key 的值。 |
2 | GET key 获取指定 key 的值。 |
3 | GETRANGE key start end 返回 key 中字符串值的子字符 |
4 | GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
5 | GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
6 | [MGET key1 key2…] 获取所有(一个或多个)给定 key 的值。 |
7 | SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
8 | SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
9 | SETNX key value 只有在 key 不存在时设置 key 的值。 |
10 | SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
11 | STRLEN key 返回 key 所储存的字符串值的长度。 |
12 | [MSET key value key value …] 同时设置一个或多个 key-value 对。 |
13 | [MSETNX key value key value …] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
14 | PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
15 | INCR key 将 key 中储存的数字值增一。 |
16 | INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。 |
17 | INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
18 | DECR key 将 key 中储存的数字值减一。 |
19 | DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。 |
20 | APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 |
4.Hash哈希相关命令
序号 | 命令及描述 |
---|---|
1 | [HDEL key field1 field2] 删除一个或多个哈希表字段 |
2 | HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。 |
3 | HGET key field 获取存储在哈希表中指定字段的值。 |
4 | HGETALL key 获取在哈希表中指定 key 的所有字段和值 |
5 | HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
6 | HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
7 | HKEYS key 获取所有哈希表中的字段 |
8 | HLEN key 获取哈希表中字段的数量 |
9 | [HMGET key field1 field2] 获取所有给定字段的值 |
10 | [HMSET key field1 value1 field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
11 | HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。 |
12 | HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。 |
13 | HVALS key 获取哈希表中所有值。 |
14 | [HSCAN key cursor MATCH pattern] [COUNT count] 迭代哈希表中的键值对。 |
5.List列表相关命令
序号 | 命令及描述 |
---|---|
1 | [BLPOP key1 key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
2 | [BRPOP key1 key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
3 | BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
4 | LINDEX key index 通过索引获取列表中的元素 |
5 | LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素 |
6 | LLEN key 获取列表长度 |
7 | LPOP key 移出并获取列表的第一个元素 |
8 | [LPUSH key value1 value2] 将一个或多个值插入到列表头部 |
9 | LPUSHX key value 将一个值插入到已存在的列表头部 |
10 | LRANGE key start stop 获取列表指定范围内的元素 |
11 | LREM key count value 移除列表元素 |
12 | LSET key index value 通过索引设置列表元素的值 |
13 | LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
14 | RPOP key 移除列表的最后一个元素,返回值为移除的元素。 |
15 | RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
16 | [RPUSH key value1 value2] 在列表中添加一个或多个值 |
17 | RPUSHX key value 为已存在的列表添加值 |
6.sorted set有序集合
序号 | 命令及描述 |
---|---|
1 | [ZADD key score1 member1 score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
2 | ZCARD key 获取有序集合的成员数 |
3 | ZCOUNT key min max 计算在有序集合中指定区间分数的成员数 |
4 | ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment |
5 | [ZINTERSTORE destination numkeys key key …] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中 |
6 | ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量 |
7 | [ZRANGE key start stop WITHSCORES] 通过索引区间返回有序集合指定区间内的成员 |
8 | [ZRANGEBYLEX key min max LIMIT offset count] 通过字典区间返回有序集合的成员 |
9 | [ZRANGEBYSCORE key min max WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员 |
10 | ZRANK key member 返回有序集合中指定成员的索引 |
11 | [ZREM key member member …] 移除有序集合中的一个或多个成员 |
12 | ZREMRANGEBYLEX key min max 移除有序集合中给定的字典区间的所有成员 |
13 | ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员 |
14 | ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员 |
15 | [ZREVRANGE key start stop WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
16 | [ZREVRANGEBYSCORE key max min WITHSCORES] 返回有序集中指定分数区间内的成员,分数从高到低排序 |
17 | ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
18 | ZSCORE key member 返回有序集中,成员的分数值 |
19 | [ZUNIONSTORE destination numkeys key key …] 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
20 | [ZSCAN key cursor MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值) |
十:菜单功能
1.根据用户ID查询菜单列表
- 在MenuMapper中写接口List
- 在IMenuService中对应此接口
- 在MenuServiceImplzhon给实现此接口
@Autowiredprivate MenuMapper menuMapper;//根据用户id查询菜单列表@Overridepublic List<Menu> getMenusByAdminId() {return menuMapper.getMenusByAdminId(((Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId());}
- 在MenuController实现方法
此处头部注解需要需改,因为表内不同
@RequestMapping("/system/cfg")
@Autowiredprivate IMenuService menuService;@ApiOperation(value = "通过用户id查询菜单列表")@GetMapping("/menu")public List<Menu> getMenusByAdminId(){return menuService.getMenusByAdminId();}
- 在MenuMapper.xml中编写sql语句
<select id="getMenusByAdminId" resultMap="Menus">SELECT DISTINCTm1.*,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 enabled2FROMt_menu m1,t_menu m2,t_admin_role ar,t_menu_role mrWHEREm1.id = m2.parentIdAND m2.id = mr.midAND mr.rid = ar.ridAND ar.adminId = #{id}AND m2.enabled = TRUEORDER BYm2.id</select>
- 在这儿编写结果集
<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>
- 测试结果:成功
key …]](https://www.runoob.com/redis/sorted-sets-zunionstore.html) 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
| 20 | [ZSCAN key cursor MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值) |
十:菜单功能
1.根据用户ID查询菜单列表
- 在MenuMapper中写接口List
- 在IMenuService中对应此接口
- 在MenuServiceImplzhon给实现此接口
@Autowiredprivate MenuMapper menuMapper;//根据用户id查询菜单列表@Overridepublic List<Menu> getMenusByAdminId() {return menuMapper.getMenusByAdminId(((Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId());}
- 在MenuController实现方法
此处头部注解需要需改,因为表内不同
@RequestMapping("/system/cfg")
@Autowiredprivate IMenuService menuService;@ApiOperation(value = "通过用户id查询菜单列表")@GetMapping("/menu")public List<Menu> getMenusByAdminId(){return menuService.getMenusByAdminId();}
- 在MenuMapper.xml中编写sql语句
<select id="getMenusByAdminId" resultMap="Menus">SELECT DISTINCTm1.*,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 enabled2FROMt_menu m1,t_menu m2,t_admin_role ar,t_menu_role mrWHEREm1.id = m2.parentIdAND m2.id = mr.midAND mr.rid = ar.ridAND ar.adminId = #{id}AND m2.enabled = TRUEORDER BYm2.id</select>
- 在这儿编写结果集
<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>
- 测试结果:成功
[外链图片转存中…(img-kuKSdn7y-1716213606716)]