項目架構:spring-boot+spring-security+thymeleafcss
1.導包(boot和thymeleaf的包請自行導入,版本號我抽的properties)前端
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2.核心配置類(完整代碼最後放出)web
2.1自定義security的配置類,而後繼承WebSecurityConfigurerAdapter這個抽象類。spring
2.2打上@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true)這兩個註解編程
2.3重寫configure(HttpSecurity http)方法,使用鏈式編程使用http.formLogin()方法設置你須要的配置(攔截參數,登陸路徑,跨域請求等)跨域
2.4重寫configure(WebSecurity web)方法,釋放靜態資源session
web.ignoring().antMatchers("/**/*.js", "/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.gif", "/**/*.map");
2.5重寫configure(AuthenticationManagerBuilder auth)方法,自定義你的登陸驗證架構
loginAuthenticationProvider:這是自定義的登陸驗證類,注入調用
customUserDetailsService:這是自定義的帳號存儲類,注入調用
customPasswordEncoder:這是自定義的密碼加密類,注入調用
loginAuthenticationProvider.setUserDetailsService(customUserDetailsService); loginAuthenticationProvider.setPasswordEncoder(customPasswordEncoder); auth.authenticationProvider(loginAuthenticationProvider); super.configure(auth);
3.security國際化問題框架
在使用security過程當中,由於原生代碼中DaoAuthenticationProvider這個類的additionalAuthenticationChecks方法ide
會在你前端頁面登錄失敗的時候返回Bad credentials這個消息。這會讓系統看上去不那麼友好。
解決方法是:
1.在resources文件夾中添加messages_zh_CN.properties這個文件。自定義你想返回前端的話。
2.自定義登陸驗證類loginAuthenticationProvider(最後放出完整代碼)
3.在自定義security的配置類中作相應配置
完整代碼:
1.自定義security配置類
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final CustomPasswordEncoder customPasswordEncoder; private final CustomUserDetailsService customUserDetailsService; private final CommonProperties commonProperties; @Autowired public SecurityConfiguration(CustomPasswordEncoder customPasswordEncoder, CustomUserDetailsService customUserDetailsService, CommonProperties commonProperties) { this.customPasswordEncoder = customPasswordEncoder; this.customUserDetailsService = customUserDetailsService; this.commonProperties = commonProperties; } @Autowired private LoginAuthenticationProvider loginAuthenticationProvider; /** * http配置 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login") .loginProcessingUrl("/doLogin") // 指定登陸頁面及成功後跳轉頁面 .successForwardUrl("/loadIndex") .and().authorizeRequests() .antMatchers(commonProperties.getAllowList()) // 上述url全部用戶均可訪問(無需登陸) .permitAll() .and() .authorizeRequests() .anyRequest() // 除上面以外的都須要登陸 .authenticated() .and() .logout() // 指定登出url .logoutUrl("/doLogout") .invalidateHttpSession(true) .and() .headers() .frameOptions() // frame框架 .sameOrigin() // csrf跨域保護忽略特殊url .and().csrf().ignoringAntMatchers(commonProperties.getAllowList()); } /** * 靜態資源放行 * @param web */ @Override public void configure(WebSecurity web) { //靜態資源過濾 web.ignoring().antMatchers("/**/*.js", "/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.gif", "/**/*.map"); } /** * 自定義認證方法 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { loginAuthenticationProvider.setUserDetailsService(customUserDetailsService); loginAuthenticationProvider.setPasswordEncoder(customPasswordEncoder); auth.authenticationProvider(loginAuthenticationProvider); super.configure(auth); } /** * 自定義認證管理類 * @return * @throws Exception */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * 自定義密碼加密類 * @return */ @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } }
2.自定義帳號存儲類
@Service public class CustomUserDetailsService implements UserDetailsService { private final AuthTokenService authTokenService; private Logger log = LoggerFactory.getLogger(this.getClass()); @Autowired public CustomUserDetailsService(AuthTokenService authTokenService) { this.authTokenService = authTokenService; } /** * 將用戶詳細信息放入security session中 * @param username * @return */ @Override public UserDetails loadUserByUsername(String username) { try{ Assert.isTrue(StringUtils.isNotBlank(username),"用戶或密碼錯誤,請從新輸入"); // 經過用戶名獲取用戶詳細信息,構造權限 UserDTO userInfo = authTokenService.getUserDetail(username); ResourceBO resource = userInfo.getResource(); List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); if(resource != null){ buildAuth(resource, authorities); } return new UserBO(userInfo, authorities); }catch (IllegalArgumentException e){ throw new UsernameNotFoundException("用戶或密碼錯誤,請從新輸入"); }catch (Exception e){ log.error("獲取用戶信息異常",e); throw new UsernameNotFoundException("用戶或密碼錯誤,請從新輸入"); } } }
3.自定義密碼加密類
@Service public class CustomPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) { return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { return s.equals(encode(charSequence)); } }
4.自定義security認證類
@Component public class LoginAuthenticationProvider extends DaoAuthenticationProvider { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private CustomPasswordEncoder customPasswordEncoder; @Autowired private void setJdbcUserDetailsService() { setUserDetailsService(customUserDetailsService); } /** * 自定義security國際化 */ @PostConstruct public void initProvider() { ReloadableResourceBundleMessageSource localMessageSource = new ReloadableResourceBundleMessageSource(); localMessageSource.setBasenames("messages_zh_CN"); messages = new MessageSourceAccessor(localMessageSource); } /** * 複寫認證失敗方法 * @param userDetails * @param authentication * @throws AuthenticationException */ @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } String presentedPassword = authentication.getCredentials().toString(); if (!customPasswordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } }