zoukankan      html  css  js  c++  java
  • 从零学习SpringSecurity

    一、简介

    SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架,和spring项目整合更加方便。

     spring-security

    二、核心功能

    • 认证(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页面,如下图

     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-2ab219137083
    
    2021-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

    登录成功如下图:

     login

    二、进阶版v2.0(配置文件配置用户密码)

    1、配置文件里面写用户名密码

    刚才的密码生成在日志里面了,实际使用很不方便,可以把密码用户名固定配置一下

    spring.security.user.name=admin
    spring.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;
    
    @EnableWebSecurity
    public 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类,增加两个方法

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                //"/**/*.html",
                "/**/*.js",
                "/**/*.css",
                "/**/*.ico",
                "/**/*.jpg",
                "/**/*.png",
                "/test/**" // 忽略test
        );
    }
    
    @Override
    protected 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")
                ;
    
    }
    

    改成新的:

    @Autowired
    ZxUserDetailsServiceImpl 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;
    
    @Component
    public 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;
    
    @Data
    public 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
    @Override
    public 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;
    
    @Service
    public 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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .httpBasic()
                ;
    
    }
    

    5、增加一个校验token的filter

    @Autowired
    ZbTokenAuthenticationFilter 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;
    
    @Service
    public 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

    @Data
    public class ZxUser extends User implements UserDetails {
    
        ...
        
        private Set<String> permissionSet;
        
        ...
    }
    

    4、修改loadUserByUsername方法

    增加权限查询部分:

    @Override
    public 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
    @Component
    public 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、配置使之生效

        
    @Autowired
    ZbPermissionEvaluator zbPermissionEvaluator;
    
    @Bean
    public DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
        defaultWebSecurityExpressionHandler.setPermissionEvaluator(zbPermissionEvaluator);
        return defaultWebSecurityExpressionHandler;
    }
    
    @Override
    protected 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-in
    Accept: */*
    Cache-Control: no-cache
    content-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/list
    Accept: application/json
    token: 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获取

     丰极

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

  • 相关阅读:
    How to function call using 'this' inside forEach loop
    jquery.validate.unobtrusive not working with dynamic injected elements
    Difference between jQuery.extend and jQuery.fn.extend?
    Methods, Computed, and Watchers in Vue.js
    Caution using watchers for objects in Vue
    How to Watch Deep Data Structures in Vue (Arrays and Objects)
    Page: DOMContentLoaded, load, beforeunload, unload
    linux bridge
    linux bridge
    EVE-NG网卡桥接
  • 原文地址:https://www.cnblogs.com/zhangbin1989/p/15030369.html
Copyright © 2011-2022 走看看