springboot對security的後端配置

  1、Spring Security是一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組能夠在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。html

  2、security和springboot也作了深度的契合,因此咱們這裏使用security來配置相關訪問。由於項目能夠作先後端的分理,我這裏就不講對於後臺處理頁面的配置了。這裏主要是講一些針對於純後端開發,進行security的相關配置,固然只是其中一部分。java

  3、講到的點主要有:跨域、認證、加密、權限控制等。web

  4、實現部分spring

  一、pom.xml須要的依賴(這裏只寫主要部分,parent等本身按須要導入依賴)編程

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

  說明:說明一點,我這裏使用的是2.0.0的版本,如何有須要能夠本身根據不一樣的版本進行配置json

  二、主要的配置部分後端

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);
    }
}

  三、csrf防禦api

  這個我這裏不詳細講,主要的目的就是每次訪問的時候除了帶上本身的訪問憑據之外,還須要帶上每次csrf的票據。固然這個是會根據具體的會話進行變化的,也就是防止csrf攻擊。跨域

  若是csrf放開配置方式能夠爲cookie安全

  即:將.csrf().disable()換成.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

  若是存在後端接口忽略的加入:.ignoringAntMatchers("/api/user/login")

  訪問的時候帶上csrf的訪問票據,攜帶方式爲下面2種方式。票據的獲取方式爲第一次訪問的時候回經過cookie的方式帶入

  request:_csrf:票據

  header:X-XSRF-TOKEN:票據

  四、跨域(配置方式見注入部分)

  跨域問題我相信在使用先後臺分理的時候確定會出現這種問題,那麼怎麼樣配置跨域問題呢!這裏提供了一種方式(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);
    }
}

  五、認證處理以及加密處理

  這裏的加密方式採用的是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);

  這種方式就能夠解決異常包裝的問題了,這裏咱們是採用的原生的配置方式。

  六、各個切入點(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;
    }
}

  其餘的就不詳細介紹了,基本上都是怎麼樣去處理,在具體的接入點出現的問題。

  七、登陸、登出

  登陸默認的參數爲username、password 採用表單方式提交。若是須要修改參數名稱能夠在loginPage後面加入

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

  說明:默認登陸、登出配置的接口不須要實現,默認也是放開的。

  八、無需驗證訪問

  在本身開發接口的時候確定不須要進行權限的訪問,這個時候就能夠經過配置方式放開具體的請求在.authorizeRequests()配置

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

  九、默認會話超時30分鐘,能夠經過配置修改會話保存時間

server:
  servlet:
    session:
      timeout: 1800s
相關文章
相關標籤/搜索