1認證
認證:辨識使用者是否本系統使用者。
優勢:1 提供多樣式的加密方法
2 提供多樣式的使用者存儲方式
3 使用者無需關注驗證封裝業務 隻需要提供查詢方法即可
4 多樣式的認證方式
5 提供使用者資訊擷取方式
可擴充的功能
1 記住我
2 郵箱驗證
3 手機驗證
4 驗證碼驗證
配置詳解
spring security統一實作WebSecurityConfigurerAdapter接口 按照以下需求添加以下配置 就可以整合成功
@Configuration
@EnableWebSecurity // 開啟springsecurity過濾鍊 filter
@EnableGlobalMethodSecurity(prePostEnabled = true) // 開啟注解方法級别權限控制
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
//實作輸入是明文 存儲到資料庫為密文 寫死即可
@Bean
public PasswordEncoder passwordEncoder() {
// 明文+随機鹽值》加密存儲
return new BCryptPasswordEncoder();
}
//使用者驗證的業務流程 也就是查詢使用者的業務代碼
@Autowired
UserDetailsService customUserDetailsService;
/**
* 認證管理器: 将上文查詢使用者是否存在的service按樣式寫入 修改service 其餘寫死 還有密碼寫死或存放記憶體中等方式 這裡不讨論
* 1. 認證資訊(使用者名,密碼)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 資料庫存儲的密碼必須是加密後的也就是必須要有passwordEncoder,不然會報錯:There is no PasswordEncoder mapped for
auth.userDetailsService(customUserDetailsService);
}
//驗證碼配置
@Autowired
private ImageCodeValidateFilter imageCodeValidateFilter;
//當驗證成功後可以傳回json或者路徑 但是現在基于前背景分離 大多數都是傳回json AuthenticationSuccessHandler為成功後轉為json的處理 按照本文配置即可
@Autowired
private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
//當驗證成功後可以傳回json或者路徑 但是現在基于前背景分離 大多數都是傳回json AuthenticationFailureHandler為失敗後轉為json的處理 按照本文配置即可
@Autowired
private AuthenticationFailureHandler customAuthenticationFailureHandler;
//建立資料源
@Autowired
DataSource dataSource;
@Autowired
private InvalidSessionStrategy invalidSessionStrategy;
/**
* 當同個使用者session數量超過指定值之後 ,會調用這個實作類
*/
@Autowired
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
/**
* 持久化token
* @return
*/
@Bean
public JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 是否啟動項目時自動建立表,true自動建立
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
/**
* 核心攔截器 當你認證成功之後 ,springsecurity它會重寫向到你上一次請求上
* 資源權限配置:
* 1. 被攔截的資源
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//調用驗證碼過濾器 下文會詳細介紹
http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin() // 表單登入方式
.loginPage("/login/page") //登入頁的頁面位址
.loginProcessingUrl("/login/form") // 登入表單送出處理url, 預設是/login
.usernameParameter("username") //預設的是 username
.passwordParameter("password") // 預設的是 password
.successHandler(customAuthenticationSuccessHandler) //登入成功傳回的json
.failureHandler(customAuthenticationFailureHandler) //登入失敗傳回的json
.and() //每個類型的配置 以.and()間隔 相當于;
.authorizeRequests() // 授權請求
.antMatchers("/login/page",
"/code/image","/mobile/page", "/code/mobile",
"/code/image",
"/code/mobile",
"/mobile/page"
).permitAll() // 放行/login/page不需要認證可通路 因為如果在調用驗證接口時還需要權限 那麼就沒有入口了 是以一些不需要登入就能通路的接口在此配置
// 此處是鑒權
// 有 sys:user 權限的可以通路任意請求方式的/role
.antMatchers("/user").hasAuthority("sys:user")
// 有 sys:role 權限的可以通路 get方式的/role
.antMatchers(HttpMethod.GET,"/role").hasAuthority("sys:role")
.antMatchers(HttpMethod.GET, "/permission")
// ADMIN 注意角色會在前面加上字首 ROLE_ , 也就是完整的是 ROLE_ADMIN, ROLE_ROOT
.access("hasAuthority('sys:premission') or hasAnyRole('ADMIN', 'ROOT')")
// 此處是鑒權
.anyRequest().authenticated() //所有通路該應用的http請求都要通過身份認證才可以通路
.and()
.rememberMe() //記住我功能
//記住功能配置
.tokenRepository(jdbcTokenRepository()) //儲存token資訊
.tokenValiditySeconds(604800) //記住我有效時長
.and()
.sessionManagement()// session管理
.invalidSessionStrategy(invalidSessionStrategy) //當session失效後的處理類 //.expiredSessionStrategy(sessionInformationExpiredStrategy)// 當使用者達到最大session數後,則調用此處的實作
.maximumSessions(1) // 每個使用者在系統中最多可以有多少個session
.maxSessionsPreventsLogin(true) // 當一個使用者達到最大session數,則不允許後面再登入
.sessionRegistry(sessionRegistry())
.and().and()
.logout()//登出相關
.addLogoutHandler(customLogoutHandler) // 退出清除緩存
.logoutUrl("/user/logout") // 退出請求路徑
.logoutSuccessUrl("/mobile/page") //退出成功後跳轉位址
.deleteCookies("JSESSIONID") // 退出後删除什麼cookie值
;// 注意不要少了分号
http.csrf().disable(); // 關閉跨站請求僞造
}
/**
* 退出清除緩存
*/
@Autowired
private CustomLogoutHandler customLogoutHandler;
/**
* 為了解決退出重新登入問題
* @return
*/
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
/**
* 一般是針對靜态資源放行
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web){
web.ignoring().antMatchers("/js/**", "/css/**");
}
}
清除緩存方法
@Component
public class CustomLogoutHandler implements LogoutHandler {
@Autowired
private SessionRegistry sessionRegistry;
@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
// 退出之後 ,将對應session從緩存中清除 SessionRegistryImpl.principals
sessionRegistry.removeSessionInformation(request.getSession().getId());
}
}
編寫user驗證方法類
需要繼承UserDetailsService 為jar包提供
1. public interface UserDetailsService {
2. UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
3. }
擷取SysUser實體方法和擷取此人菜單的方法 這裡不多說
public abstract class AbstractUserDetailsService implements UserDetailsService {
@Autowired
private SysPermissionService sysPermissionService;
/**
* 這個方法交給子類去實作它,查詢使用者資訊
*
* @param usernameOrMobile 使用者名或者手機号
* @return
*/
public abstract SysUser findSysUser(String usernameOrMobile);
@Override
public UserDetails loadUserByUsername(String usernameOrMobile) throws UsernameNotFoundException {
// 1. 通過請求的使用者名去資料庫中查詢使用者資訊
SysUser sysUser = findSysUser(usernameOrMobile);
// 通過使用者id去擷取權限資訊
findSysPermission(sysUser);
return sysUser;
}
private void findSysPermission(SysUser sysUser) {
if (sysUser == null) {
throw new UsernameNotFoundException("使用者名或密碼錯誤");
}
// 2. 查詢該使用者有哪一些權限
List<SysPermission> permissions = sysPermissionService.findByUserId(sysUser.getId());
if (CollectionUtils.isEmpty(permissions)) {
return;
}
// 在左側菜單 動态渲染會使用,目前先把它都傳入
sysUser.setPermissions(permissions);
// 3. 封裝權限資訊
List<GrantedAuthority> authorities = Lists.newArrayList();
for (SysPermission sp : permissions) {
// 權限辨別
String code = sp.getCode();
authorities.add(new SimpleGrantedAuthority(code));
}
sysUser.setAuthorities(authorities);
}
}
@Component("customUserDetailsService")
public class CustomUserDetailsService extends AbstractUserDetailsService{
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired // 不能删掉,不然報錯
PasswordEncoder passwordEncoder;
@Autowired
SysUserService sysUserService;
@Override
public SysUser findSysUser(String usernameOrMobile) {
logger.info("請求認證的使用者名: " + usernameOrMobile);
// 1. 通過請求的使用者名去資料庫中查詢使用者資訊
return sysUserService.findByUsername(usernameOrMobile);
}
}
CustomAuthenticationSuccessHandler與CustomAuthenticationFailureHandler
//此類沒有注入,因為樓主并沒有使用
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired(required = false) // 容器中可以不需要有接口的實作,如果有則自動注入
AuthenticationSuccessListener authenticationSuccessListener;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if(authenticationSuccessListener != null) {
// 當認證之後 ,調用此監聽,進行後續處理,比如加載使用者權限菜單
authenticationSuccessListener.successListener(request, response, authentication);
}
if(LoginResponseType.JSON.equals(
"post")) {
// 認證成功後,響應JSON字元串
MengxueguResult result = MengxueguResult.ok("認證成功");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(result.toJsonString());
}else {
//重定向到上次請求的位址上,引發跳轉到認證頁面的位址
logger.info("authentication: " + JSON.toJSONString(authentication));
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
/**
*
* @param exception 認證失敗時抛出異常
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if(LoginResponseType.JSON.equals("post")) {
// 認證失敗響應JSON字元串,
MengxueguResult result = MengxueguResult.build(HttpStatus.UNAUTHORIZED.value(), exception.getMessage());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(result.toJsonString());
}else {
// 重寫向回認證頁面,注意加上 ?error
// super.setDefaultFailureUrl(securityProperties.getAuthentication().getLoginPage()+"?error");
// 擷取上一次請求路徑
String referer = request.getHeader("Referer");
logger.info("referer:" + referer);
// 如果下面有值,則認為是多端登入,直接傳回一個登入位址
Object toAuthentication = request.getAttribute("toAuthentication");
String lastUrl = toAuthentication != null ? "/login/page"
: StringUtils.substringBefore(referer,"?");
logger.info("上一次請求的路徑 :" + lastUrl);
super.setDefaultFailureUrl(lastUrl+"?error");
super.onAuthenticationFailure(request, response, exception);
}
}
}