Spring Boot整合Spring Security


Spring Boot對於該家族的框架支持良好,但是當中本人作為小白配置還是有一點點的小問題,這里分享一下。這個項目是使用之前發布的Spring Boot會員管理系統重新改裝,將之前filter登錄驗證改為Spring Security
  

1. 配置依賴

  Spring Boot框架整合Spring Security只需要添加相應的依賴即可,其后都是配置Spring Security。
  這里使用Maven開發,依賴如下:

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

**2. 配置Spring Security **

**2.1 定制WebSecurityConfigurerAdapter **

下面通過Java配置Spring Security不攔截靜態資源以及需要登錄驗證等各種信息

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //開啟方法上的認證
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private CustomerUserDetailsService userDetailsService;
    @Resource
    private CustomerLoginSuccessHandler successHandler;
    @Resource
    private BCryptPasswordEncoder encoder;

    @Bean
    public BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**"); //不過濾靜態資源
        super.configure(web);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService) //注冊自己定制的UserDetailsService
                .passwordEncoder(encoder); // 配置密碼加密器
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests() //獲取請求方面的驗證器
                    .antMatchers("/", "/error").permitAll()// 訪問當前配置的路徑可通過認證
                    //訪問其他路徑需要認證和角色權限
                    .anyRequest().hasAnyAuthority(AdminRole.G_ADMIN.toString(), AdminRole.S_ADMIN.toString())
                    .anyRequest().authenticated()
                    .and()
                .formLogin() //獲取登錄認證驗證器
                    .loginPage("/login") //注冊自定義的登錄頁面URL
                    .failureForwardUrl("/login") //登錄失敗后以登錄時的請求轉發到該鏈接
                    .successHandler(successHandler) //登錄成功后調用該處理器
                    .permitAll() //登錄請求給予通過認證
                    .and()
                .logout() //推出登錄
                    .logoutSuccessUrl("/login") //退出后訪問URL
                    .and()
                .csrf().disable(); //關閉csrf,默認開啟

    }
}

  上面的類是用於配置Spring Security框架的,這里有關於幾個東西要說明下:

  • @EnableGlobalMethodSecurity
      這是用於配置類 / 方法上的安全認證,它默認關閉了,我們現在配置了@EnableGlobalMethodSecurity(prePostEnabled = true)這將使得可以在方法上使用注解@PreAuthorize("hasAnyAuthority('S_ADMIN')")(使用范例可看AdminController)在沒調用注解下的方法時判斷當前認證用戶有沒有S_ADMIN角色權限操作該方法。
      這個注解可以使得在開發非Web項目時起到作用。

  • configure(AuthenticationManagerBuilder auth)
      這里可以配置該項目與用戶的關聯,也稱作認證。我們需要構建自己的登錄驗證器,這里我們自定義了一個UserDetailsService和一個密碼加密器。

  • **configure(HttpSecurity http) **
      這里可以配置我們對於一個HttpSecurity需要通過什么樣子的安全認證。代碼中還包含了csrf().disable(),這是因為框架默認開啟了csrf,這樣我們的ajax和表單提交等都需要提供一個token,為了偷懶,所以,你懂的

還有就是一個小tip,這是一個配置,相當於xml,Spring只會加載一次,所以以上的方法Spring是初始化一次的

2.2 定制UserDetailsService

  在上面中我們注冊了一個自定義的UserDetailsService,這是用於當用戶認證時我們需要提供一個可被框架識別的UserDetails,用這個UserDetails和我們的登錄用戶實體類建立起一個關聯,讓框架可以處理我們的用戶信息,框架所提供的只是usernamepassword,它只是幫助我們認證我們的用戶和密碼是否匹配。定制UserDetailsService代碼如下:

@Component //注冊為Spring組件
public class CustomerUserDetailsService implements UserDetailsService{

    @Resource
    private AdminDao adminDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //通過dao查找當前用戶名對應的用戶
        Admin admin = adminDao.findAdminByUsername(username);
        if (admin == null){
            throw new UsernameNotFoundException("This username: "+username+"is not exist");
        }
        //返回一個定制的UserDetails
		//AuthorityUtils.createAuthorityList(admin.getRole())就是將我們該用戶所有的權限(角色)生成一個集合
        return new CustomerUserDetails(admin, AuthorityUtils.createAuthorityList(admin.getRole()));
        
    }
}

  當我們登錄時,org.springframework.security.authentication.dao.DaoAuthenticationProvider會調用loadUserByUsername方法並且把當前需要認證的用戶名傳入,我們需要的就是放回一個通過該用戶名得出的擁有密碼信息的UserDetails,這樣,框架就可以幫助我們驗證需要認證的密碼與查出的密碼是否匹配。

2.3 定制UserDetails

  要讓Spring Security能夠識別我們定制的UserDetails,那就需要按照它的標准來寫,所以,我們需要實現一個UserDetails接口,具體代碼如下:

public class CustomerUserDetails implements UserDetails {

    private Admin admin = null;
    //存放權限的集合
    private final Collection<? extends GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    public CustomerUserDetails(Admin admin, Collection<? extends GrantedAuthority> authorities) {
        this(admin, true, true, true,true,authorities);
    }

    public CustomerUserDetails(Admin admin, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        if(admin.getUsername() != null && !"".equals(admin.getUsername()) && admin.getPassword() != null) {
            this.admin = admin;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
            this.authorities = authorities;
        } else {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }

    public Admin getAdmin() {
        return admin;
    }

    public void setAdmin(@NotNull Admin admin) {
        this.admin = admin;
    }

    public boolean equals(Object rhs) {
        return rhs instanceof CustomerUserDetails && this.getUsername().equals(((CustomerUserDetails) rhs).getUsername());
    }

    public int hashCode() {
        return this.getUsername().hashCode();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.admin.getPassword();
    }

    @Override
    public String getUsername() {
        return this.admin.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

  關於以上的寫法是參照Spring Security所提供的User類來改寫的,具體代碼可看org.springframework.security.core.userdetails.User源碼。
  在org.springframework.security.core.userdetails.User中重寫hashCodeequals可能是為了判斷重復登錄的問題,當然了,這只是個人意淫,純屬瞎猜。為了安全起見我也跟着重寫了那兩個方法了。
  在定制UserDetails我加入了一個成員變量admin,這是因為之前的開發中沒有使用該框架,只是使用了filter登錄驗證,將登錄信息存放到 session,所以,為了不大改之前的舊東西,所以我還將會從這里獲取admin存於session

2.4 定制AuthenticationSuccessHandler

  定制AuthenticationSuccessHandler不是直接繼承接口,而是繼承一個實現類SavedRequestAwareAuthenticationSuccessHandler,因為這里保存了我們的登錄前請求信息,我們可以不用獲取RequestCache就可實現直接從登錄頁面重定向到登錄前訪問的URL,具體代碼如下:

@Component
public class CustomerLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //SecurityContextHolder是Spring Security的核心組件,可獲取框架愛內的一些信息
        //這里我得到登錄成功后的UserDetails
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            request.getSession().setAttribute("admin", ((CustomerUserDetails) principal).getAdmin());
        }
        super.onAuthenticationSuccess(request, response, authentication);
    }
}

  關於上面代碼中的框架核心組件,可以到官方文檔中的此鏈接查看
  定制這個處理器的主要目的是因為我想要偷懶,因為我模板引擎需要訪問admin來獲取用戶名,當然了也可以使用該表達式${session.SPRING_SECURITY_CONTEXT.authentication.principal.username}來獲取UserDetails的用戶名

3. 總結

  寫到這里就差不多該結束了,這里我就做下個人總結。這次整合Spring Security中途不是像這篇文章那樣如此下來的,中間磕磕碰碰,所以呢,我就在想,是不是我只是停留在了應用層面才導致的這么個結果,如果了解源碼多一點就可能不會出現這些問題了。

以上的代碼已上傳GitHub

可參考鏈接
https://docs.spring.io/spring-security/site/docs/5.0.3.RELEASE/reference/htmlsingle/
https://blog.csdn.net/u283056051/article/details/55803855
http://www.tianshouzhi.com/api/tutorials/spring_security_4/250


免責聲明!

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



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