一、简介
SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架,和spring我的项目整合更加不便。
二、外围性能
- 认证(Authentication):指的是验证某个用户是否拜访该零碎。
- 受权(Authorization):指的是验证某个用户是否有权限执行某个操作。
三、搭建v1.0版本
1、新建一个springboot我的项目
myspringsecurity
2、增加maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>
3、新建一个test的controller
package com.zb.myspringsecurity.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/demo")public class DemoController { @RequestMapping("/hello") public String hello() { return "hello world"; }}
4、启动我的项目
MyspringsecurityApplication.main();
5、用浏览器测试
http://localhost:8080/demo/hello
咱们会发现浏览器会跳转到login页面,如下图
6、明码登陆
咱们能够在我的项目启动日志外面找到明码
2021-07-19 10:58:48.558 INFO 5244 --- [ main] .s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]2021-07-19 10:58:48.558 INFO 5244 --- [ main] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]2021-07-19 10:58:48.684 INFO 5244 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'2021-07-19 10:58:48.812 INFO 5244 --- [ main] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: ced4127a-1677-438e-a65b-2ab2191370832021-07-19 10:58:48.868 INFO 5244 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@40021799, org.springframework.security.web.context.SecurityContextPersistenceFilter@2d7e1102, org.springframework.security.web.header.HeaderWriterFilter@3fbfa96, org.springframework.security.web.csrf.CsrfFilter@61533ae, org.springframework.security.web.authentication.logout.LogoutFilter@4a699efa, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4482469c, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4917d36b, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@4a1c0752, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@278f8425, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2adddc06, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4ebadd3d, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@332f25c8, org.springframework.security.web.session.SessionManagementFilter@466d49f0, org.springframework.security.web.access.ExceptionTranslationFilter@599f571f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7004e3d]2021-07-19 10:58:48.911 INFO 5244 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''2021-07-19 10:58:48.914 INFO 5244 --- [ main] c.z.m.MyspringsecurityApplication : Started MyspringsecurityApplication in 1.458 seconds (JVM running for 2.405)
- 用户名是:user
- 明码(从日志找到)是:ced4127a-1677-438e-a65b-2ab219137083
登录胜利如下图:
二、进阶版v2.0(配置文件配置用户明码)
1、配置文件外面写用户名明码
方才的明码生成在日志外面了,理论应用很不不便,能够把明码用户名固定配置一下
spring.security.user.name=adminspring.security.user.password=123
- 重新启动我的项目,会发现没有生成明码的日志了
- 测试用新的用户名明码没问题
三、v3.0(java类外面写用户名明码)
1、在java类外面配置用户名明码
方才是写在配置文件外面,咱们还能够写到java类外面
package com.zb.myspringsecurity.config.security;import org.springframework.context.annotation.Bean;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;@EnableWebSecuritypublic class ZbWebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("zhangsan") .password(passwordEncoder().encode("123")) .roles("ADMIN") .and() .withUser("lisi") .password(passwordEncoder().encode("123")) .roles("ADMIN") .and() .withUser("wangwu") .password(passwordEncoder().encode("123")) .roles("ADMIN") ; } }
咱们再重启我的项目测试一下,发现用三个用户名明码都没问题。
四、v4.0(ignore url)
1、批改下面的java类,减少两个办法
@Overridepublic void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( //"/**/*.html", "/**/*.js", "/**/*.css", "/**/*.ico", "/**/*.jpg", "/**/*.png", "/test/**" // 疏忽test );}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic();}
- 咱们配置了ignore的url
- 咱们再加一个controller用来测试
package com.zb.myspringsecurity.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test")public class TestController { @RequestMapping("/test") public String hello() { return "hello test"; }}
- 留神咱们下面的ignore外面有:"/test/**"
- 也就是说TestController 不会有登录校验
重启我的项目测试一下没问题
五、v5.0(在数据库外面配置用户名明码)
1、新建mysql库
create database myspringsecurity CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;create user securityuser IDENTIFIED by 'securitypass';grant all privileges on myspringsecurity.* to securityuser@localhost identified by 'securitypass';flush privileges;
2、新建用户表和角色表
DROP TABLE IF EXISTS `tb_user`;CREATE TABLE `tb_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_name` varchar(50) DEFAULT NULL,`password` varchar(100) DEFAULT NULL,`mobile` int(11) DEFAULT NULL,`sex` int(2) DEFAULT NULL,`email` varchar(50) DEFAULT NULL,`status` int(2) DEFAULT NULL,`create_time` DATE DEFAULT NULL,`create_id` int(11) DEFAULT NULL,`update_time` date DEFAULT NULL,`update_id` int(11) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `tb_role`;CREATE TABLE `tb_role` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`role_name` varchar(50) DEFAULT NULL,`status` int(2) DEFAULT NULL,`create_time` DATE DEFAULT NULL,`create_id` int(11) DEFAULT NULL,`update_time` date DEFAULT NULL,`update_id` int(11) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `tr_user_role`;CREATE TABLE `tr_user_role` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_id` bigint(20) DEFAULT NULL,`role_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
3、初始化一些数据
insert into `tb_user` (id, user_name, password) values (1, 'zhangsan', '123');insert into `tb_user` (id, user_name, password) values (2, 'lisi', '123');insert into `tb_user` (id, user_name, password) values (3, 'wangwu', '123');insert into `tb_role` (id, role_name) values (1, '系统管理员');insert into `tb_role` (id, role_name) values (2, '个别操作员');insert into `tr_user_role` (id, user_id, role_id) values (1, 1, 1);insert into `tr_user_role` (id, user_id, role_id) values (2, 1, 2);insert into `tr_user_role` (id, user_id, role_id) values (3, 2, 2);
4、退出maven依赖
咱们这里用了mybatis-plus。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version></dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.2</version></dependency>
5、引入mybatis-plus主动生成代码的依赖
<!-- mybatis-plus代码生成 --><dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.29</version></dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.2</version></dependency>
6、批改代码生成类
package com.zb.myspringsecurity.config.mybatis;import com.baomidou.mybatisplus.core.toolkit.StringPool;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.InjectionConfig;import com.baomidou.mybatisplus.generator.config.*;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;public class MybatisGenerator { public static void main(String[] args) { AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); final String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("system"); gc.setOpen(false); gc.setFileOverride(true); gc.setBaseResultMap(true); // gc.setSwagger2(true); 实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/myspringsecurity?useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("securityuser"); dsc.setPassword("securitypass"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setParent("com.zb.myspringsecurity"); 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 + "/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.underline_to_camel); // strategy.setSuperEntityClass("com.baomidou.ant.common.BaseEntity"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(false); // 公共父类 // strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController"); // 写于父类中的公共字段 // strategy.setSuperEntityColumns("id"); strategy.setInclude("tb_user","tb_role","tr_user_role"); // strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix("tb_", "tr_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); }}
- 执行生成mapper,service和controller
- mybatis筹备好了,能够批改security配置了
7、批改configure办法
原先的:
public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("zhangsan") .password(passwordEncoder().encode("123")) .roles("ADMIN") .and() .withUser("lisi") .password(passwordEncoder().encode("123")) .roles("ADMIN") .and() .withUser(passwordEncoder().encode("123")) .password("123") .roles("ADMIN") ;}
改成新的:
@AutowiredZxUserDetailsServiceImpl zxUserDetailsService;public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(zxUserDetailsService);}
8、新增ZxUserDetailsServiceImpl类
package com.zb.myspringsecurity.config.security;import com.zb.myspringsecurity.entity.Role;import com.zb.myspringsecurity.entity.User;import com.zb.myspringsecurity.entity.UserRole;import com.zb.myspringsecurity.service.IUserRoleService;import com.zb.myspringsecurity.service.IUserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;import org.springframework.util.Assert;import org.springframework.util.CollectionUtils;import java.util.ArrayList;import java.util.List;@Componentpublic class ZxUserDetailsServiceImpl implements UserDetailsService { @Autowired PasswordEncoder passwordEncoder; @Autowired IUserService iUserService; @Autowired IUserRoleService iUserRoleService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { /** // DEMO: List<SimpleGrantedAuthority> authorityList = new ArrayList<>(); authorityList.add(new SimpleGrantedAuthority("Admin")); ZxUser zxUser = new ZxUser(); zxUser.setUserName("zhangsanfeng"); zxUser.setPassword(passwordEncoder.encode("123")); zxUser.setAuthorities(authorityList); return zxUser; */ List<User> userList = iUserService.lambdaQuery().eq(User::getUserName, username).list(); if (CollectionUtils.isEmpty(userList)) { throw new UsernameNotFoundException("不存在的用户"); } User user = userList.get(0); ZxUser zxUser = new ZxUser(); zxUser.setUserName(user.getUserName()); zxUser.setPassword(passwordEncoder.encode(user.getPassword())); zxUser.setId(user.getId()); List<UserRole> userRoleList = iUserRoleService.lambdaQuery().eq(UserRole::getUserId, zxUser.getId()).list(); List<SimpleGrantedAuthority> authorityList = new ArrayList<>(); if (!CollectionUtils.isEmpty(userRoleList)) { for (UserRole userRole : userRoleList) { authorityList.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId()))); } } zxUser.setAuthorities(authorityList); return zxUser; }}
9、新增自定义user
package com.zb.myspringsecurity.config.security;import com.zb.myspringsecurity.entity.User;import lombok.Data;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;@Datapublic class ZxUser extends User implements UserDetails { private Collection<? extends GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return super.getPassword(); } @Override public String getUsername() { return super.getUserName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }}
10、验证
重启服务,用数据库外面的用户和明码验证没问题。
六、v6.0(实现前后端拆散,token校验)
1、引入认证管理器 bean
/*** 认证管理器* @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean();}
2、写一个对立的登录接口
package com.zb.myspringsecurity.controller;import com.zb.myspringsecurity.config.security.ZxUser;import com.zb.myspringsecurity.config.security.ZxUserDetailsServiceImpl;import com.zb.myspringsecurity.config.vo.CommonResponse;import com.zb.myspringsecurity.config.vo.LoginParamVo;import com.zb.myspringsecurity.config.vo.TokenVo;import com.zb.myspringsecurity.service.TokenService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@Slf4j@RestController@RequestMapping("/login")public class LoginController { @Autowired private AuthenticationManager authenticationManager; @Resource ZxUserDetailsServiceImpl userDetailsService; @Resource TokenService tokenService; @RequestMapping("/login-in") public CommonResponse<TokenVo> login(@RequestBody LoginParamVo loginParamVo) { try { // 1 创立UsernamePasswordAuthenticationToken UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginParamVo.getUsername(), loginParamVo.getPassword()); // 2 认证 Authentication authentication = this.authenticationManager.authenticate(token); // 3 保留认证信息 SecurityContextHolder.getContext().setAuthentication(authentication); // 4 加载UserDetails ZxUser zxUser = this.userDetailsService.loadUserByUsername(loginParamVo.getUsername()); // 5 生成自定义token TokenVo tokenVo = tokenService.createToken(zxUser); return CommonResponse.successWithData(tokenVo); } catch (Exception e) { return CommonResponse.fail(401, e.getMessage()); } }}
3、tokenservice
package com.zb.myspringsecurity.service;import com.zb.myspringsecurity.config.vo.TokenVo;import org.springframework.security.core.userdetails.UserDetails;public interface TokenService { TokenVo createToken(UserDetails details); boolean verifyToken(String token); String getUserNameByToken(String token);}
简略的实现:
package com.zb.myspringsecurity.service.impl;import com.zb.myspringsecurity.config.vo.TokenVo;import com.zb.myspringsecurity.service.TokenService;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Service;import java.util.HashMap;import java.util.Map;import java.util.UUID;@Servicepublic class TokenServiceImpl implements TokenService { // todo 能够存redis, 设置过期工夫 private static final Map<String, String> tokenMap = new HashMap<>(); @Override public TokenVo createToken(UserDetails details) { String token = UUID.randomUUID().toString(); tokenMap.put(token, details.getUsername()); TokenVo tokenVo = new TokenVo(); tokenVo.setToken(token); tokenVo.setExpireTime(60*60); return tokenVo; } @Override public boolean verifyToken(String token) { return tokenMap.get(token) != null; } @Override public String getUserNameByToken(String token) { return tokenMap.get(token); }}
- 创立token, 校验token, 依据token获取username
- 存的是token和username的关系
4、批改校验形式
批改为:SessionCreationPolicy.STATELESS
@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic() ;}
5、减少一个校验token的filter
@AutowiredZbTokenAuthenticationFilter zbTokenAuthenticationFilter;httpSecurity.addFilterBefore(zbTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
filter:
package com.zb.myspringsecurity.config.security.customer;import com.zb.myspringsecurity.config.security.ZxUser;import com.zb.myspringsecurity.config.security.ZxUserDetailsServiceImpl;import com.zb.myspringsecurity.service.IUserRoleService;import com.zb.myspringsecurity.service.TokenService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.stereotype.Service;import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Servicepublic class ZbTokenAuthenticationFilter extends OncePerRequestFilter { @Autowired TokenService tokenService; @Autowired IUserRoleService iUserRoleService; @Autowired ZxUserDetailsServiceImpl userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { logger.info("TokenAuthenticationFilter.doFilterInternal start ..."); String token = request.getHeader("token"); if (token == null || "".equals(token)) { logger.info("token is null , return ."); filterChain.doFilter(request, response); return; } if (SecurityContextHolder.getContext().getAuthentication() != null) { filterChain.doFilter(request, response); return; } boolean result = tokenService.verifyToken(token); if (!result) { logger.info("ssoService.verifyToken not pass , return ."); filterChain.doFilter(request, response); return; } ZxUser zxUser = userDetailsService.loadUserByUsername(tokenService.getUserNameByToken(token)); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( zxUser, null, zxUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); logger.info("token valid pass , username : " + zxUser.getUsername()); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); }}
- 至此,咱们曾经把我的项目革新成前后端拆散的了
- 有数据库用户明码登录
- 有登录生成token
- 有校验url, header必须蕴含token
七、v7.0(权限管制)
咱们下面曾经把spring security的一个外围性能(认证)说完了,上面咱们说受权。
1、减少权限表,关联表
DROP TABLE IF EXISTS `tb_permission`;CREATE TABLE `tb_permission` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`en_name` varchar(50) DEFAULT NULL,`cn_name` varchar(50) DEFAULT NULL,`create_time` DATE DEFAULT NULL,`create_id` int(11) DEFAULT NULL,`update_time` date DEFAULT NULL,`update_id` int(11) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `tr_role_permission`;CREATE TABLE `tr_role_permission` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`role_id` bigint(20) DEFAULT NULL,`permission_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2、初始化数据
insert into `tb_permission` (id, en_name, cn_name) values (1, 'system:user:read', '可读');insert into `tb_permission` (id, en_name, cn_name) values (2, 'system:user:edit', '可批改');insert into `tr_role_permission` (id, role_id, permission_id) values (1, 1, 1);insert into `tr_role_permission` (id, role_id, permission_id) values (2, 1, 2);insert into `tr_role_permission` (id, role_id, permission_id) values (3, 2, 1);
3、批改ZxUser
减少permissionSet
@Datapublic class ZxUser extends User implements UserDetails { ... private Set<String> permissionSet; ...}
4、批改loadUserByUsername办法
减少权限查问局部:
@Overridepublic ZxUser loadUserByUsername(String username) throws UsernameNotFoundException { List<User> userList = iUserService.lambdaQuery().eq(User::getUserName, username).list(); if (CollectionUtils.isEmpty(userList)) { throw new UsernameNotFoundException("不存在的用户"); } User user = userList.get(0); ZxUser zxUser = new ZxUser(); zxUser.setUserName(user.getUserName()); zxUser.setPassword(passwordEncoder.encode(user.getPassword())); zxUser.setId(user.getId()); List<SimpleGrantedAuthority> authorityList = new ArrayList<>(); // role List<UserRole> userRoleList = iUserRoleService.lambdaQuery().eq(UserRole::getUserId, zxUser.getId()).list(); if (!CollectionUtils.isEmpty(userRoleList)) { for (UserRole userRole : userRoleList) { authorityList.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId()))); } // permission List<Long> roleIdList = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList()); List<RolePermission> rolePermissionList = iRolePermissionService.lambdaQuery() .in(RolePermission::getRoleId, roleIdList).list(); if (!CollectionUtils.isEmpty(rolePermissionList)) { Collection<Permission> permissionList = iPermissionService .listByIds(rolePermissionList.stream() .map(RolePermission::getPermissionId).collect(Collectors.toList())); Set<String> permissionSet = permissionList.stream().map(Permission::getEnName).collect(Collectors.toSet()); zxUser.setPermissionSet(permissionSet); } } zxUser.setAuthorities(authorityList); return zxUser;}
5、咱们实现一个本人的权限管制类
package com.zb.myspringsecurity.config.security.customer;import com.zb.myspringsecurity.config.security.ZxUser;import lombok.extern.slf4j.Slf4j;import org.springframework.security.access.PermissionEvaluator;import org.springframework.security.core.Authentication;import org.springframework.stereotype.Component;import java.io.Serializable;import java.util.Set;@Slf4j@Componentpublic class ZbPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { ZxUser user = (ZxUser) authentication.getPrincipal(); Set<String> permissonSet = user.getPermissionSet(); if (permission == null) { log.info("permission valid not pass , permission is null"); return false; } if (permissonSet.contains(permission.toString())) { log.info("permission valid pass , permission : {}", permission.toString()); return true; } log.info("permission valid not pass , permission : {}", permission.toString()); return false; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return false; }}
- 很简略,就是判断权限汇合存不存在以后权限
6、配置使之失效
@AutowiredZbPermissionEvaluator zbPermissionEvaluator;@Beanpublic DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() { DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler(); defaultWebSecurityExpressionHandler.setPermissionEvaluator(zbPermissionEvaluator); return defaultWebSecurityExpressionHandler;}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception { ... httpSecurity.authorizeRequests().expressionHandler(defaultWebSecurityExpressionHandler()); ...}
7、测试类
package com.zb.myspringsecurity.controller;import com.zb.myspringsecurity.entity.User;import com.zb.myspringsecurity.service.IUserService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.util.List;@Slf4j@RestController@RequestMapping("/user")public class UserController { @Autowired IUserService iUserService; @PreAuthorize("hasPermission('UserController', 'system:user:read')") @RequestMapping("/list") public List<User> list(HttpServletRequest request) { log.info("session id: {}" , request.getSession().getId()); return iUserService.list(); }}
8、启动服务测试
- 留神:咱们下面的/user/list, 配置了'system:user:read'权限
- 认真去看咱们初始化的数据库数据,会发现wangwu是没有任何角色的,也没有任何权限
- 所以,wangwu 不能拜访/user/list
先测试zhangsan:
POST http://localhost:8080/login/login-inAccept: */*Cache-Control: no-cachecontent-type:application/json{"username":"zhangsan", "password":"123"}
返回:
{ "data": { "token": "e048ba23-7061-43d6-ab35-7c2eb93acda8", "expireTime": 3600 }, "code": 200, "msg": "ok"}
用这个token去申请/user/list
GET http://localhost:8080/user/listAccept: application/jsontoken: e048ba23-7061-43d6-ab35-7c2eb93acda8
返回:
[ { "id": 1, "userName": "zhangsan", "password": "123", "mobile": null, "sex": null, "email": null, "status": null, "createTime": null, "createId": null, "updateTime": null, "updateId": null }, { "id": 2, "userName": "lisi", "password": "123", "mobile": null, "sex": null, "email": null, "status": null, "createTime": null, "createId": null, "updateTime": null, "updateId": null }, { "id": 3, "userName": "wangwu", "password": "123", "mobile": null, "sex": null, "email": null, "status": null, "createTime": null, "createId": null, "updateTime": null, "updateId": null }]
- 用同样的办法测试lisi和wangwu,lisi能够拜访,wangwu不能够,返回如下
{ "timestamp": "2021-07-19T06:53:38.833+0000", "status": 500, "error": "Internal Server Error", "message": "No message available", "path": "/user/list"}
- 当然你还能够对立你的异样回复信息,能够自行钻研
- 至此,咱们的权限管制也实现了
八、总结
- 至此,咱们的认证和鉴权都说完了
- 以上只是一种实现,spring security反对自定义扩大,还有其它实现形式,能够本人钻研
- 代码放到github上了,关注公众号:丰极,回复:myspringsecurity获取
欢送关注微信公众号:丰极,更多技术学习分享。