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

     丰极

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

  • 相关阅读:
    [hihocoder1509][异或排序]
    [hdu6148][Valley Numer]
    [hdu2089][不要62]
    [luoguU42591][小T的绝对值]
    [luogu2073][送花]
    [bzoj4709][柠檬]
    [luogu2114][起床困难综合症]
    [codevs3342][绿色通道]
    [luoguU42591][小T的面试题]
    [noip][2014]
  • 原文地址:https://www.cnblogs.com/zhangbin1989/p/15030369.html
Copyright © 2011-2022 走看看