SpringSecurity(1)---認證+授權程式碼實現

雨點的名字發表於2020-06-03

認證+授權程式碼實現

Spring Security是 一種基於 Spring AOP 和 Servlet 過濾器的安全框架。它提供全面的安全性解決方案,同時在 Web 請求級和方法呼叫級處理身份確認和授權。

有關認證和授權的理論知識,之前有寫過相關部落格。瞭解許可權管理

一、SpringSceurity工作流程

網上找一張圖,覺得畫的挺好的,比較容易理解。不然換的是原始碼流程圖很難去理解。

SpringSecurity(1)---認證+授權程式碼實現

圖片地址 : 地址 可以單機放大看更加清楚

要想理解這張圖建議看下這篇部落格,因為這張圖中需要自定義的My...類,在文章中都有說明,所以更好理解點。

Spring Boot Security 詳解


二、認證+授權程式碼

這裡只展示一些核心程式碼,具體完整程式碼放在github上。

1、UserDetails介面

Security 中的使用者介面,我們自定義使用者類要實現該介面, 用於向security中注入當前使用者的姓名密碼,和擁有的角色。同時也包含一些其它資訊,比如當前使用者是否過期,

賬號是否鎖定等等。

自己定義User實現這個介面

public class User implements UserDetails {
    private String username;
    private String password;
    private List<Role> roles;
    /**
     * 獲取使用者名稱
     */
    @Override
    public String getUsername() {
        return username;
    }
    /**
     * 獲取密碼
     */
    @Override
    public String getPassword() {
        return password;
    }
    /**
     * 使用者的許可權集, 預設需要新增ROLE_ 字首
     */
    @Override
    @JsonIgnore
    public List<GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        }
        return authorities;
    }
    /**
     * 賬戶是否過期
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }
    /**
     * 賬戶是否鎖定
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }
    /**
     * 憑證是否過期
     */
    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }
    /**
     * 使用者是否可用
     */
    @Override
    public boolean isEnabled() {
        return true;
    }  
}

2、UserDetailsService

Security 中的使用者 Service,自定義使用者服務類需要實現該介面。這個介面只有一個方法需要我們去實現,那就是通過使用者名稱去獲取使用者資訊。這裡也是和資料庫互動獲取

使用者認證和授權資訊的地方。

@Service
@Slf4j
public class UserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //TODO 正常應該查詢資料庫獲取使用者和使用者的許可權
//        User user = userMapper.loadUserByUsername(userName);
//        List<Role> roles = rolesMapper.getRolesByUid(user.getId());
//        user.setRoles(roles);
        log.info("登陸使用者名稱: {}", userName);
        //通過使用者名稱查詢到的密碼 密碼肯定是加密過的 這裡明文密碼是 123456
        String password = "e10adc3949ba59abbe56e057f20f883e";
        //使用者對應許可權
        List<Role> roles = Lists.newArrayList(new Role(1L, "教師"), new Role(2L, "學生"));
        User user = new User(userName, password, roles);
        return user;
    }
}

注意 這裡的明文密碼是 123456,也就是使用者輸入這個才能完成認證。授權的話當前使用者有兩個角色 教師學生。在下面測試的時候會用到。

3、WebSecurityConfigurerAdapter

它是Spring Security的Java 配置類。建立類SecurityConfiguration繼承 WebSecurityConfigurerAdapter,來對我們應用中所有的安全相關的事項(

所有url,驗證使用者名稱密碼,表單重定向等)進行控制。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 1、配置的是認證資訊, AuthenticationManagerBuilder 這個類,就是AuthenticationManager的建造者, 我們只需要向這個類中, 配置使用者資訊,
     *    就能生成對應的AuthenticationManager, 這個類也提過,是使用者身份的管理者, 是認證的入口, 因此,我們需要通過這個配置,想security提供真實的使用者身份。
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    }
    /**
     * 2、配置Security的認證策略, 每個模組配置使用and結尾。這個也是最複雜的
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    }
    /**
     * 3、這個配置方法用於配置靜態資源的處理方式,可使用 Ant 匹配規則。就是可以不用認證就可以直接訪問的介面
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
    }
}

完整示例

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;
    /**
     * 密碼驗證器
     */
    @Autowired
    private PassWordEncorder passWordEncorder;
    /**
     * 成功處理器
     */
    @Autowired
    private AuthenctiationSuccessHandler authenctiationSuccessHandler;

    /**
     * 失敗處理器
     */
   @Autowired
   private AuthenctiationFailHandler authenctiationFailHandler;
   /**
    * 向Security注入使用者資訊
    */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passWordEncorder);
    }
    /**
     * 配置規則
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //開啟登陸配置
        http.authorizeRequests()
                // 登入之後就能訪問
                .antMatchers("/no-authorize").authenticated()
                // 登陸後 需要校長角色許可權
                .antMatchers("/need-authorize").hasRole("校長")
                // 其他的路徑都是登入後即可訪問
                .anyRequest().authenticated()
                .and().formLogin()
                // 定義登入頁面,未登入時,訪問一個需要登入之後才能訪問的介面,會自動跳轉到該頁面
                .loginPage("/login_page")
                //登入成功的處理器
                .successHandler(authenctiationSuccessHandler)
                //登入失敗的處理器
                .failureHandler(authenctiationFailHandler)
                // 登入處理介面
                .loginProcessingUrl("/login")
                // 定義登入時,使用者名稱的 key,預設為 username
                .usernameParameter("username")
                //定義登入時,使用者密碼的 key,預設為 password
                .passwordParameter("password").permitAll()
                .and().logout()
                ////和表單登入相關的介面統統都直接通過
                .permitAll()
                .and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
    }

    /**
     * 對於/static/  下的路徑都不用認證
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/no-login");
    }

    /**
     * 使用者未認證異常攔截
     */
    @Bean
    AccessDeniedHandler getAccessDeniedHandler() {
        return new AuthenticationAccessDeniedHandler();
    }
}

注意 這裡一共配置了三個路徑用於測試。

1、/no-login 介面不需要認證就可以直接訪問
2、/no-authorize 需要認證 但不需要授權就可以訪問
3、/need-authorize 首先需要認證 認證通過還需要授權 這裡需要校長的角色才可以訪問該介面 但是我們測試使用者只有教師和學生所以沒有許可權訪問該介面

下面會針對這個個介面分別進行測試。


三、測試

1、介面提供

@RestController
public class TestController {

    /**
     * 1、不需要登陸就可以訪問
     */
    @RequestMapping(value = "/no-login")
    public ServiceResponse noLogin() {
        return ServiceResponse.success("歡迎訪問不需要登陸介面");
    }
    /**
     * 2、只登陸,不許認證介面
     */
    @RequestMapping(value = "/no-authorize")
    public ServiceResponse needAuthorize(){
        return ServiceResponse.success("登陸了 不用授權");
    }
    /**
     * 3、登陸 + 相關認證介面
     */
    @RequestMapping(value = "/need-authorize")
    public ServiceResponse noAuthorize() {
        return ServiceResponse.success("登陸+授權成功");
    }
    /**
     * @Description: 如果自動跳轉到這個頁面,說明使用者未登入,返回相應的提示即可
     */
    @RequestMapping("/login_page")
    public ServiceResponse loginPage() {
        return  ServiceResponse.failure("001", "尚未登入,請登入!");
    }
}

2、未登入訪問 no-login 和 no-authorize 介面

no-login介面

SpringSecurity(1)---認證+授權程式碼實現

很明顯沒有登陸 請求該介面成功!

no-authorize介面

SpringSecurity(1)---認證+授權程式碼實現

沒有登陸訪問失敗,在上面配置瞭如果使用者沒有認證的話跳轉到login_page介面,所以這裡返回 '尚未登入,請登入!'

3、登陸後訪問 no-authorize 和 need-authorize 介面

先登陸

根據上面配置登陸的路徑為 /login 請求引數包括 usernamepassword

SpringSecurity(1)---認證+授權程式碼實現

注意 這裡需要post請求。

no-authorize 介面

SpringSecurity(1)---認證+授權程式碼實現

登陸就可以訪問了。

need-authorize 介面

SpringSecurity(1)---認證+授權程式碼實現

雖然登陸成功了,但是因為該介面需要校長角色,之前給該使用者只配置了教師和學生的角色所以訪問失敗。


參考

1、SpringSide 3 中的安全框架

2、Spring Security 工作原理概覽

3、Spring Boot Security 詳解 很贊



別人罵我胖,我會生氣,因為我心裡承認了我胖。別人說我矮,我就會覺得好笑,因為我心裡知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(17)

相關文章