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
posted @ 2019-01-14 17:13  小不点丶  阅读(5962)  评论(0编辑  收藏  举报