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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM