天天看點

Spring Security 注解方式開發(基于SSM)

本次踩坑長達4小時...由于感覺以xml配置的方式來配置security不太靈活, 于是開始了踩坑之旅.

一. 引入Maven依賴

<!--
    <spring.version>4.2.4.RELEASE</spring.version>
-->

<!--Spring Security-->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.version}</version>
</dependency>
           

二. 建立加載使用者資訊的類,實作UserDetailsService接口

package com.xxxx.xxx.xxx.impl;

import com.usoft.scenery.mapper.AdminRoleMapper;
import com.usoft.scenery.pojo.AdminUser;
import com.usoft.scenery.pojo.Role;
import com.usoft.scenery.pojo.vo.AdminRole;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 根據使用者名從資料庫擷取對應的管理者資訊,從資訊中取出角色清單并添加到授權中
 *
 * @author Braycep
 * @date 2019/3/6 12:53
 */

@Service(value = "userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource(name = "adminRoleMapper")
    private AdminRoleMapper adminRoleMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        LoggerFactory.getLogger(UserDetailsServiceImpl.class).info("使用者:username=" + s + " 請求授權");
        AdminRole adminRole = adminRoleMapper.findOne(s);
        if (adminRole != null) {
            List<AdminUser> adminUser = adminRole.getAdminUsers();
            List<Role> role = adminRole.getRoles();
            if (adminUser != null && adminUser.size() == 1) {
                if (role != null && role.size() > 0) {
                    List<GrantedAuthority> list = new ArrayList<>();
                    for (Role r : role) {
                        list.add(new SimpleGrantedAuthority(r.getMark()));
                    }

                    AdminUser admin = adminUser.get(0);
                    if (admin != null && admin.getState() == 1) {
                        if (admin.getStartTime().getTime() < System.currentTimeMillis() && System.currentTimeMillis() < admin.getEndTime().getTime()) {
                            return new User(s, admin.getPassword(), list);
                        }
                    }
                }
            }
        }
        return null;
    }
}
           

三. 建立配置類

package com.xxx.xxx.xxx.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;

/**
 * Security的配置類
 *
 * @author Braycep
 * @date 2019/3/6 14:09
 */
@Configuration
@EnableWebSecurity(debug = false)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //自定義的身份認證處理類,實作UserDetailsService接口
    @Resource(name = "userDetailsService")
    private UserDetailsService userDetailsService;

    //資料庫連接配接池,用于使用記住我的功能
    @Autowired
    private DruidDataSource dataSource;

    //security自帶的密碼加密類
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //用于對記住我這個功能生成token
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

    //配置通路
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                //放行
                .antMatchers("/fonts/**", "/css/**", "/images/**", "/js/**", "login.html").permitAll()
                //角色 SU 可以通路"/html/**"
                //.antMatchers("/html/**").hasRole("SU")
                //已經認證過的就可以通路
                .anyRequest().authenticated()
                .and()
                .headers()
                //設定頁面frame分區的政策
                .frameOptions().sameOrigin()
                .and()
                //使用security的/login登入驗證, loginProcessingUrl的值就是表單送出的位址
                .formLogin().loginProcessingUrl("/login").loginPage("/login.html")
                .defaultSuccessUrl("/index.html")
                .failureForwardUrl("/login.html?failed")
                .failureUrl("/login.html?failed")
                .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login.html")
                //記住密碼
                .and()
                .rememberMe()
                .tokenRepository(persistentTokenRepository())//設定操作表的Repository
                .tokenValiditySeconds(60 * 60 * 24 * 7)//設定記住我的時間為7天
                .userDetailsService(userDetailsService);//設定userDetailsService

        // 加http.csrf().disable()是為了防止報這個錯Whitelabel Error Page
        //This application has no explicit mapping for /error, so you are seeing this as a fallback.
        http.csrf().disable();
    }

    //設定密碼加密的方式
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(getBCryptPasswordEncoder());
    }
}
           

四. 注冊security配置

package com.xxx.xxx.xxx.config;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

/**
 * @author Braycep
 * @date 2019/3/6 15:31
 */
public class SecurityWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //2.1建立AnnotationConfigWebApplicationContext
        AnnotationConfigWebApplicationContext springContext = new AnnotationConfigWebApplicationContext();
        //2.2spring的配置類  springSecurity的配置類
        springContext.register(WebSecurityConfig.class);
    }
}
           

五. 建立初始化類, 必須的

package com.xxx.xxx.xxx.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

/**
 * @author Braycep
 * @date 2019/3/6 15:44
 */
public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
    //隻繼承就好
}
           

前端的代碼注意表單的送出位址, 應該和loginProcessingUrl中的參數一緻, 其餘的html隻是成功或者失敗的跳轉頁面, loginPage是登入頁面, 不是登入送出位址, 并且, 表單隻能post送出; /logout這接口一旦請求就會登出, 并跳轉到loginPage

如果要使用xml配置檔案的方式來使用Security, 就需要在web.xml中去加載它, 這裡不用在web.xml中加載

記住我功能的資料庫表是固定的 

create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null);
           

然後前端的複選框的屬性name也要固定為 'remember-me'

<input name="remember-me" type="checkbox" title="記住密碼">
           

資料庫存儲結果:  由Spring Security控制

Spring Security 注解方式開發(基于SSM)

然後部署測試, OK

Spring Security 注解方式開發(基于SSM)