SpringBoot+SpringSecurity+Druid解決CSRF開啓問題

SpringBoot+Druid整合配置如下

1、pom中引入依賴

<!-- Druid 數據連接池依賴 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.27</version>
</dependency>

2、yml中配置數據源

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://xx.xx.xx.xx:3306/xxx?useUnicode=true&characterEncoding=utf-8
    username: root
    password: xxxxxxxx
    type: com.alibaba.druid.pool.DruidDataSource
    # 下面爲連接池的補充設置,應用到上面所有數據源中
    # 初始化大小,最小,最大
    initialSize: 20
    minIdle: 20
    maxActive: 100
    # 配置獲取連接等待超時的時間
    maxWait: 60000
    # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一個連接在池中最小生存的時間,單位是毫秒
    minEvictableIdleTimeMillis: 30000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打開PSCache,並且指定每個連接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    # 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
    filters: stat,wall,slf4j
    # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    # 合併多個DruidDataSource的監控數據
    #useGlobalDataSourceStat: true

以上配置根據自己的需要修改

3、配置監控系統(此處有兩種方法,這裏只介紹一種,因爲我不喜歡在主類上加太多東西。。。)

使用代碼註冊Servlet

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
public class DruidConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(DruidConfiguration.class);

    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
    @Value("${spring.datasource.maxWait}")
    private int maxWait;
    @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;
    @Value("${spring.datasource.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;
    @Value("${spring.datasource.validationQuery}")
    private String validationQuery;
    @Value("${spring.datasource.testWhileIdle}")
    private boolean testWhileIdle;
    @Value("${spring.datasource.testOnBorrow}")
    private boolean testOnBorrow;
    @Value("${spring.datasource.testOnReturn}")
    private boolean testOnReturn;
    @Value("${spring.datasource.poolPreparedStatements}")
    private boolean poolPreparedStatements;
    @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
    private int maxPoolPreparedStatementPerConnectionSize;
    @Value("${spring.datasource.filters}")
    private String filters;
    @Value("{spring.datasource.connectionProperties}")
    private String connectionProperties;

    @Bean     //聲明其爲Bean實例
    @Primary  //在同樣的DataSource中,首先使用被標註的DataSource
    public DataSource dataSource(){
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        //configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            System.err.println("druid configuration initialization filter: "+ e);
        }
        datasource.setConnectionProperties(connectionProperties);
        return datasource;
    }

    @Bean
    public ServletRegistrationBean druidServlet() {
        logger.info("init Druid Servlet Configuration ");
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        // IP白名單
        //servletRegistrationBean.addInitParameter("allow", "192.168.1.xxx,127.0.0.1");
        // IP黑名單(共同存在時,deny優先於allow)
        //servletRegistrationBean.addInitParameter("deny", "192.168.1.xxx");
        //控制檯管理用戶
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "admin");
        //是否能夠重置數據 禁用HTML頁面上的“Reset All”功能
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

}


配置了以上內容後,我認爲就可以了,於是訪問localhost:8080/druid,也進入了登錄頁,於是我輸入用戶名和密碼admin,admin,點擊sign in,怎麼點都沒反應,於是搜索爲什麼,發現沒有。。。。只能從系統裏面再調試,折騰一番,發現是SpringSecurity的CSRF導致的。。。

這裏有兩個辦法,一個是關閉CSRF,不過這種辦法不友好,還有就是對druid開放,示例代碼如下:

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.http.HttpServletRequest;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/hello").hasRole("USER").and()
                //.csrf().disable() //關閉CSRF
                .csrf().requireCsrfProtectionMatcher(new RequestMatcher() {
                    @Override
                    public boolean matches(HttpServletRequest httpServletRequest) {
                        String servletPath = httpServletRequest.getServletPath();
                        if (servletPath.contains("/druid")) {
                            return false;
                        }
                        return true;
                    }
                }).and()
            .formLogin().loginPage("/login").defaultSuccessUrl("/hello").and()
            .logout().logoutUrl("/logout").logoutSuccessUrl("/login");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}
再去訪問,解決了。。。。


————————更正——————————

上面這樣,會導致將原有的驗證覆蓋掉,會出現GET等原先放行的請求無法成功,需改爲如下:

.csrf().requireCsrfProtectionMatcher(new RequestMatcher() {
    //放行這幾種請求
    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
    //放行rest請求,當然後面rest與web將會分開,到時這裏可以刪除
    private RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("^/rest/.*", null);

    @Override
    public boolean matches(HttpServletRequest request) {
        if(allowedMethods.matcher(request.getMethod()).matches()){
            return false;
        }

        String servletPath = request.getServletPath();
        if (servletPath.contains("/druid")) {
            return false;
        }
        return !unprotectedMatcher.matches(request);
    }
})


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章