Spring Security學習(二)

以下配置基於表單登錄配置

自定義配置登錄頁面

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
             // 自定義頁面路徑
            .loginPage("/api/login")
            .and()
            .authorizeRequests()
            // 允許/api/login的URL訪問 否則瀏覽器頁面將提示重定向次數過多進入死循環
            .antMatchers("/api/login").permitAll()
            .anyRequest()
            .authenticated();
}

自定義登錄路徑

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
             .loginPage("/api/login")
            // 自定義登錄路徑
            .loginProcessingUrl("/api/formlogin") 
            .and()
            .authorizeRequests()
            .antMatchers("/api/login").permitAll()
            .anyRequest()
            .authenticated();
}

Spring Security 默認表單登錄

public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
}

可設置loginProcessingUrl屬性來替換默認登錄地址

登錄成功處理

當用戶登錄成功後需要保存用戶登錄數據,比如IP地址,登錄時間等

// 注入SecurityLoginSuccessHandler
@Autowired
private SecurityLoginSuccessHandler securityLoginSuccessHandler; 

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/api/login")
            // 自定義登錄成功處理
            .successHandler(securityLoginSuccessHandler)
            .loginProcessingUrl("/api/formlogin")
            .and()
            .authorizeRequests()
            .antMatchers("/api/login","/static/*").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .csrf().disable();
}

SecurityLoginSuccessHandler

@Component
public class SecurityLoginSuccessHandler implements AuthenticationSuccessHandler {
    // 重定向跳轉類
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    // 緩存請求路徑,可獲取攔截前的請求路徑
    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("登錄成功");
        // 獲取攔截前請求路徑 並通過RedirectStrategy進行重定向跳轉
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        String redirectUrl = savedRequest.getRedirectUrl();

         // todo do something

        this.redirectStrategy.sendRedirect(request, response, redirectUrl);
    }
}

登錄失敗處理

跟登錄成功處理一樣 只是實現接口不同而已

@Autowired
private SecurityLoginFailHandler securityLoginFailHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/api/login")
            .successHandler(securityLoginSuccessHandler)
            // 自定義登錄失敗處理
            .failureHandler(securityLoginFailHandler)
            .loginProcessingUrl("/api/formlogin")
            .and()
            .authorizeRequests()
            .antMatchers("/api/login", "/static/*").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .csrf().disable();
}

SecurityLoginFailHandler

@Component
public class SecurityLoginFailHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        System.out.println("登錄失敗");

        // todo do something

        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/json;charset=UTF-8");
        response.getWriter().write(exception.getMessage());
    }
}

退出登錄

默認是訪問URL/logout將註銷登陸的用戶

自定義配置

@Autowired
private SecurityLoginoutHandler securityLoginoutHandler;
@Autowired
private SecurityLoginoutSuccessHandler securityLoginoutSuccessHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/api/login")
            .successHandler(securityLoginSuccessHandler)
            .failureHandler(securityLoginFailHandler)
            .loginProcessingUrl("/api/formlogin")
            .and()
            .authorizeRequests()
            .antMatchers("/api/login", "/static/*").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .logout()
            // 退出登錄地址
            .logoutUrl("/logout")
            // 退出跳轉路徑
            .logoutSuccessUrl("/api/login")
            // 默認session失效
            .invalidateHttpSession(true)
            // 退出登錄處理
            .addLogoutHandler(securityLoginoutHandler)
            // 退出登錄成功處理
            .logoutSuccessHandler(securityLoginoutSuccessHandler)
            // 指定退出登錄後需要刪除的cookie名稱,多個cookie之間以逗號分隔。 
//                .deleteCookies(cookieNamesToClear)
            .and()
            .csrf().disable();
}

SecurityLoginoutHandler

用來執行必要的清理,因而他們不應該拋出錯誤

@Component
public class SecurityLoginoutHandler implements LogoutHandler {
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        System.out.println("退出登錄");
    }
}

SecurityLoginoutSuccessHandler

被LogoutFilter在成功註銷後調用,用來進行重定向或者轉發相應的目的地。注意這個接口與LogoutHandler幾乎一樣,但是可以拋出異常。

@Component
public class SecurityLoginoutSuccessHandler implements LogoutSuccessHandler {
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("退出登錄成功執行");
        this.redirectStrategy.sendRedirect(request, response, "/");
    }
}

自定義用戶登錄賬號密碼

WebSecurityConfig

@Autowired
private SecurityUserDetailService securityUserDetailService;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    // 在內存中寫死用戶數據
    auth.inMemoryAuthentication().withUser("123456").password("123456").roles("USER");
    // 動態獲取用戶數據
    auth.userDetailsService(securityUserDetailService);
}

SecurityUserDetailService

/**
 * 自定義UserDetailService
 *
 * @author Peng
 */
@Component
public class SecurityUserDetailService implements UserDetailsService {
    /**
     * String password; 密碼
     * String username; 賬戶名
     * Set<GrantedAuthority> authorities; 角色名
     * boolean accountNonExpired; 賬戶沒有過期
     * boolean accountNonLocked; 帳號沒有被鎖定
     * boolean credentialsNonExpired; 密碼沒有過期
     * boolean enabled; 是否可用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 這裏寫死的密碼 實際應用中應該從數據庫中獲取 
        User user = new User(username, "123456", true,
                true,
                true,
                true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
        return user;
    }
}

自定義用戶登錄賬號密碼(密碼加密)

WebSecurityConfig

@Autowired
private SecurityUserDetailService securityUserDetailService;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    // 在內存中寫死用戶數據
    auth.inMemoryAuthentication().withUser("123456").password("123456").roles("USER");
    // 動態獲取用戶數據 使用Security加密
    auth.userDetailsService(securityUserDetailService).passwordEncoder(new BCryptPasswordEncoder());

    // 配置驗證方式爲加密驗證 
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(securityUserDetailService);
    authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
    auth.authenticationProvider(authenticationProvider);
}

SecurityUserDetailService

/**
 * 自定義UserDetailService
 *
 * @author Peng
 */
@Component
public class SecurityUserDetailService implements UserDetailsService {
    /**
     * String password; 密碼
     * String username; 賬戶名
     * Set<GrantedAuthority> authorities; 角色名
     * boolean accountNonExpired; 賬戶沒有過期
     * boolean accountNonLocked; 帳號沒有被鎖定
     * boolean credentialsNonExpired; 密碼沒有過期
     * boolean enabled; 是否可用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 這裏寫死的密碼 實際應用中應該從數據庫中獲取 
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        // 數據庫創建用戶數據時密碼應通過Security加密後保存
        String password = encoder.encode("123456");
        System.out.println(password);
        User user = new User(username, password, true,
                true,
                true,
                true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
        return user;
    }
}

PasswordEncoder

// 密碼加密
String encode(CharSequence rawPassword);
// 驗證密碼是否吻合
boolean matches(CharSequence rawPassword, String encodedPassword);

獲取登錄用戶信息

/**
 * 獲取登錄用戶數據
 */
@RequestMapping("/api/getme")
@ResponseBody
public Object getLoginUser(Authentication authentication) {
    Map<String, Object> json = new HashMap<>();
    // 兩種方法
    json.put("data", SecurityContextHolder.getContext().getAuthentication());
    json.put("data1", authentication);
    return json;
}

資源權限限制

WebSecurityConfig

 .antMatchers("/api/me").hasRole("ADMIN")

 這樣只要是訪問“api/me”的路徑就會驗證用戶身份,用戶身份必須是ADMIN才允許訪問
表達 描述
hasRole([role]) 如果當前主體具有指定的角色,則返回true.
hasAnyRole([role1,role2]) 如果當前的主體有任何提供的角色(給定的作爲一個逗號分隔的字符串列表)的話,返回true
hasAuthority([authority]) 如果當前的主體具有指定的權限,則返回 true.
hasAnyAuthority([authority1,authority2]) 如果當前的主體有任何提供的角色(給定的作爲一個逗號分隔的字符串列表)的話,返回true.
principal 允許直接訪問表示當前用戶的主對象
permitAll 允許所有用戶訪問
denyAll 不允許用戶訪問
isAnonymous() 如果當前的主體是一個匿名用戶,則返回true.
isRememberMe() 如果當前的主體是一個匿名用戶,則返回true
isAuthenticated() 如果用戶不是匿名的,則返回 true
isFullyAuthenticated() 如果用戶不是一個匿名的或是一個記住我的用戶返回true
hasPermission(Object target, Object permission) 如果用戶已訪問給定權限的提供的目標,則返回true,例如hasPermission(domainObject, ‘read’)
hasPermission(Object targetId, String targetType, Object permission) 如果用戶已訪問給定權限的提供的目標,則返回true,例如hasPermission(1, ‘com.example.domain.Message’, ‘read’)

登錄驗證碼

首先得寫一個獲取驗證碼方法,方法能夠生成隨機驗證碼並且把驗證碼存入session供驗證的功能,這裏就不寫了

因爲Spring Security未提供驗證碼的接口,所以需要我們自己寫一個過濾器處理

VaildCodeFilter 驗證碼校驗過濾器

/**
 * Security 登錄驗證碼驗證
 *
 * @author Peng
 */
public class VaildCodeFilter extends OncePerRequestFilter {
    private SecurityLoginFailHandler securityLoginFailHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 登錄路徑必須爲/api/formlogin才生效
        if ("/api/formlogin".equals(request.getRequestURI())) {
            HttpSession session = request.getSession();
            try {
                String code = request.getParameter(頁面驗證碼字段);
                String sessionCode = (String) session.getAttribute(驗證碼sessionId);
                if (code == null || sessionCode == null) {
                    throw new VaildCodeException("驗證碼不存在");
                }
                if (!sessionCode.equals(code)) {
                    throw new VaildCodeException("驗證碼不匹配");
                }
            } catch (VaildCodeException e) {
                securityLoginFailHandler.onAuthenticationFailure(request, response, e);
                return;
            }
            session.removeAttribute(驗證碼sessionId);
        }
        filterChain.doFilter(request, response);
    }

    public void setSecurityLoginFailHandler(SecurityLoginFailHandler securityLoginFailHandler) {
        this.securityLoginFailHandler = securityLoginFailHandler;
    }
}

VaildCodeException 驗證碼驗證異常類

/**
 * 驗證碼異常類
 *
 * @author Peng
 */
public class VaildCodeException extends AuthenticationException {
    public VaildCodeException(String explanation) {
        super(explanation);
    }
}

最後在WebSecurityConfig configure中配置

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 在UsernamePasswordAuthenticationFilter過濾器前添加自定義驗證碼過濾器
        VaildCodeFilter vaildCodeFilter = new VaildCodeFilter();
        vaildCodeFilter.setSecurityLoginFailHandler(securityLoginFailHandler);
        http.addFilterBefore(vaildCodeFilter, UsernamePasswordAuthenticationFilter.class);

        http.formLogin().loginPage("/api/login")
                .successHandler(securityLoginSuccessHandler)
                .failureHandler(securityLoginFailHandler)
                .loginProcessingUrl("/api/formlogin");
        http.authorizeRequests()
                .antMatchers("/api/login", "/static/*", "/api/codeImg").permitAll()
                .antMatchers("/api/me").hasRole("ADMIN")
                .anyRequest()
                .authenticated();
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/api/login")
                .invalidateHttpSession(true)
                .addLogoutHandler(securityLoginoutHandler)
                .logoutSuccessHandler(securityLoginoutSuccessHandler);
        // 指定退出登錄後需要刪除的cookie名稱,多個cookie之間以逗號分隔。
//                .deleteCookies(cookieNamesToClear)
        http.csrf().disable();
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章