以下配置基於表單登錄配置
自定義配置登錄頁面
@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();
}