• springboot对security的后端配置


      一、Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

      二、security和springboot也做了深度的契合,所以我们这里使用security来配置相关访问。因为项目可以做前后端的分理,我这里就不讲对于后台处理页面的配置了。这里主要是讲一些针对于纯后端开发,进行security的相关配置,当然只是其中一部分。

      三、讲到的点主要有:跨域、认证、加密、权限控制等。

      四、实现部分

      1、pom.xml需要的依赖(这里只写主要部分,parent等自己按需要导入依赖)

         <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>

      说明:说明一点,我这里使用的是2.0.0的版本,如何有需要可以自己根据不同的版本进行配置

      2、主要的配置部分

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
    import org.springframework.security.web.csrf.CsrfFilter;
    import org.springframework.web.filter.CorsFilter;
    
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        //跨域
        @Autowired
        private CorsFilter corsFilter;
    
        //认证处理类
        @Autowired
        private DaoAuthenticationProvider daoAuthenticationProvider;
    
        //认证成功
        @Autowired
        private AuthenticationSuccessHandler successHandler;
    
        //认证失败
        @Autowired
        private AuthenticationFailureHandler failureHandler;
    
        //登出成功
        @Autowired
        private LogoutSuccessHandler logoutSuccessHandler;
    
        @Autowired
        private AccessDeniedHandler deniedHandler;
    
        //认证EntryPoint
        @Autowired
        private AuthenticationEntryPoint entryPoint;
    
        @Override
        protected void configure(AuthenticationManagerBuilder builder) throws Exception {
            builder.authenticationProvider(daoAuthenticationProvider);
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()
                    .antMatchers(HttpMethod.OPTIONS, "/api/**")
                    .antMatchers("/swagger-ui.html")
                    .antMatchers("/webjars/**")
                    .antMatchers("/swagger-resources/**")
                    .antMatchers("/v2/**");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .csrf()
                .disable()
                .addFilterBefore(corsFilter, CsrfFilter.class)
                .exceptionHandling()
                .authenticationEntryPoint(entryPoint)
                .accessDeniedHandler(deniedHandler)
            .and()
                .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .formLogin().loginPage("/api/user/login")
                .successHandler(successHandler)
                .failureHandler(failureHandler)
            .and()
                .logout().logoutUrl("/api/user/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
            .and()
                .headers()
                .frameOptions()
                .disable()
            .and()
                .sessionManagement().maximumSessions(1800);
        }
    }

      3、csrf防护

      这个我这里不详细讲,主要的目的就是每次访问的时候除了带上自己的访问凭据以外,还需要带上每次csrf的票据。当然这个是会根据具体的会话进行变化的,也就是防止csrf攻击。

      如果csrf放开配置方式可以为cookie

      即:将.csrf().disable()换成.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

      如果存在后端接口忽略的加入:.ignoringAntMatchers("/api/user/login")

      访问的时候带上csrf的访问票据,携带方式为下面2种方式。票据的获取方式为第一次访问的时候回通过cookie的方式带入

      request:_csrf:票据

      header:X-XSRF-TOKEN:票据

      4、跨域(配置方式见注入部分)

      跨域问题我相信在使用前后台分理的时候肯定会出现这种问题,那么怎么样配置跨域问题呢!这里提供了一种方式(CorsFilter.class)

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Configuration
    public class CorsFilterConfiguration {
    
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            List<String> allowedOrigins = new ArrayList<>();
            allowedOrigins.add("*");
            List<String> allowedMethods = new ArrayList<>();
            allowedMethods.add("*");
            List<String> allowedHeaders = new ArrayList<>();
            allowedHeaders.add("*");
            List<String> exposedHeaders = new ArrayList<>();
            exposedHeaders.add("Link");
            exposedHeaders.add("X-Total-Count");
            corsConfiguration.setAllowedOrigins(allowedOrigins);
            corsConfiguration.setAllowedMethods(allowedMethods);
            corsConfiguration.setAllowedHeaders(allowedHeaders);
            corsConfiguration.setExposedHeaders(exposedHeaders);
            corsConfiguration.setAllowCredentials(true);
            corsConfiguration.setMaxAge(1800L);
            source.registerCorsConfiguration("/api/**", corsConfiguration);return new CorsFilter(source);
        }
    }

      5、认证处理以及加密处理

      这里的加密方式采用的是springsecurity提供的一种加密方式:BCryptPasswordEncoder(hash、同一密码加密不一样的密钥),认证配置见builder部分

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    @Configuration
    public class PasswordEncoderConfiguration {
    
        /**
         * 密码加密
         */
        @Bean
        public BCryptPasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    import com.cetc.domain.Role;
    import com.cetc.domain.User;
    import com.cetc.repository.UserRepository;
    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.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Service
    @Transactional
    public class AuthDetailsService implements UserDetailsService {
    
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userRepository.findByUsername(username);
            if (user == null){
                throw new UsernameNotFoundException("用户不存在!");
            }
            List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
            List<Role> roles = user.getRoles();
            if (roles != null && !roles.isEmpty()) {
                roles.stream().forEach(role -> simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role.getRoleType())));
            }
            return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), simpleGrantedAuthorities);
        }
    }

      说明:这里的UsernameNotFoundException如果是默认配置,是不能被处理类所捕获的。原因:DaoAuthenticationProvider父类AbstractUserDetailsAuthenticationProviderhideUserNotFoundExceptionstrue

      解决方式重新配置:DaoAuthenticationProvider 

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    @Configuration
    public class CustomDaoAuthenticationProvider {
    
        @Autowired
        private AuthDetailsService authDetailsService;
    
        @Autowired
        private BCryptPasswordEncoder passwordEncoder;
    
        @Bean
        public DaoAuthenticationProvider daoAuthenticationProvider() {
            DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
            provider.setUserDetailsService(authDetailsService);
            provider.setPasswordEncoder(passwordEncoder);
            provider.setHideUserNotFoundExceptions(false);
            return provider;
        }
    }

      然后修改builder.userDetailsService(authDetailsService).passwordEncoder(passwordEncoder);builder.authenticationProvider(provider);

      这种方式就可以解决异常包装的问题了,这里我们是采用的原生的配置方式。

      6、各个切入点(AuthenticationEntryPoint、AccessDeniedHandler、AuthenticationSuccessHandler、AuthenticationFailureHandler、LogoutSuccessHandler)五个切入点,作用就是在对应操作过后,可以根据具体的切入点进行相应异常的处理

    import com.alibaba.fastjson.JSONObject;
    import com.cetc.constant.SystemErrorCode;
    import com.cetc.dto.MenuDTO;
    import com.cetc.result.ResponseMsg;
    import com.cetc.service.IUserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.MediaType;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
    
    import java.util.List;
    
    @Configuration
    public class CustomHandlerConfiguration {
    
        @Autowired
        private IUserService userService;
    
        /**
         * 访问接入点处理
         * @return
         */
        @Bean
        public AuthenticationEntryPoint authenticationEntryPoint() {
            AuthenticationEntryPoint entryPoint = (request, response, e) -> {
                ResponseMsg<String> responseMsg = new ResponseMsg<>();
                responseMsg.setStatus(false);
                responseMsg.setBody(e.getMessage());
                responseMsg.setErrorCode(SystemErrorCode.NO_LOGIN);
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.getWriter().write(JSONObject.toJSONString(responseMsg));
            };
            return entryPoint;
        }
    
        /**
         * 接入过后问题处理
         * @return
         */
        @Bean
        public AccessDeniedHandler accessDeniedHandler() {
            AccessDeniedHandler accessDeniedHandler = (request, response, e) -> {
                ResponseMsg<String> responseMsg = new ResponseMsg<>();
                responseMsg.setStatus(false);
                responseMsg.setBody(e.getMessage());
                responseMsg.setErrorCode(SystemErrorCode.NO_PERMISSION);
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.getWriter().write(JSONObject.toJSONString(responseMsg));
            };
            return accessDeniedHandler;
        }
    
        /**
         * 登录成功后的处理
         * @return
         */
        @Bean
        public AuthenticationSuccessHandler authenticationSuccessHandler() {
            AuthenticationSuccessHandler authenticationSuccessHandler = (request, response, authentication) -> {
                //返回数据
                ResponseMsg<List<MenuDTO>> responseMsg = new ResponseMsg<>();
                responseMsg.setBody(userService.getMenus());
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.getWriter().write(JSONObject.toJSONString(responseMsg));
            };
            return authenticationSuccessHandler;
        }
    
        /**
         * 登录失败后的处理
         * @return
         */
        @Bean
        public AuthenticationFailureHandler authenticationFailureHandler() {
            AuthenticationFailureHandler authenticationFailureHandler = (request, response, e) -> {
                ResponseMsg<String> responseMsg = new ResponseMsg<>();
                responseMsg.setStatus(false);
                responseMsg.setBody(e.getMessage());
                responseMsg.setErrorCode(SystemErrorCode.ACCOUNT_ERROR);
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.getWriter().write(JSONObject.toJSONString(responseMsg));
            };
            return authenticationFailureHandler;
        }
    
        /**
         * 登出成功后的处理
         * @return
         */
        @Bean
        public LogoutSuccessHandler logoutSuccessHandler() {
            LogoutSuccessHandler logoutSuccessHandler = (request, response, authentication) -> {
                ResponseMsg<String> responseMsg = new ResponseMsg<>();
                responseMsg.setStatus(true);
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.getWriter().write(JSONObject.toJSONString(responseMsg));
            };
            return logoutSuccessHandler;
        }
    }

      其他的就不详细介绍了,基本上都是怎么样去处理,在具体的接入点出现的问题。

      7、登录、登出

      登录默认的参数为username、password 采用表单方式提交。如果需要修改参数名称可以在loginPage后面加入

    .usernameParameter("name")
    .passwordParameter("pwd")

      说明:默认登录、登出配置的接口不需要实现,默认也是放开的。

      8、无需验证访问

      在自己开发接口的时候肯定不需要进行权限的访问,这个时候就可以通过配置方式放开具体的请求在.authorizeRequests()配置

    .antMatchers("/api/user/register").permitAll()

      9、默认会话超时30分钟,可以通过配置修改会话保存时间

    server:
      servlet:
        session:
          timeout: 1800s
  • 相关阅读:
    操作系统(生产者、消费者问题)
    Hibernate——离线查询
    计算机网络(物理层习题)
    Mysql(笛卡尔积、等值连接、自然连接、外连接)
    vim文本编辑器——文件导入、命令查找、导入命令执行结果、自定义快捷键、ab命令、快捷键的保存
    vim文本编辑器——替换、保存退出
    CT107D电路解析
    vim文本编辑器——删除、复制、剪切、更改某一个字符、替换、撤销、关键字搜索
    文本编辑器vim——三种模式、显示行号、插入命令、行快速定位、行内定位
    关机、重启、退出登录命令
  • 原文地址:https://www.cnblogs.com/ll409546297/p/10267926.html
Copyright © 2020-2023  润新知