一、简介

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获取

欢送关注微信公众号:丰极,更多技术学习分享。