SpringSecurity(五)圖片驗證碼的使用
阿新 • • 發佈:2018-11-19
SpringSecurity預設是沒有圖片驗證碼功能的,假如我們需要在登入介面新增一個圖片驗證碼的功能,我們可以在UsernamePasswordAuthenticationFilter過濾器之前寫一個圖片驗證碼過濾器,圖片驗證碼過濾器的功能:首先判斷請求地址是否需要圖片驗證碼,如果需要就判斷圖片驗證碼是否正確,如果驗證碼正確則繼續往下執行,否則丟擲驗證碼錯誤異常;如果不需要就直接往下執行。
圖片驗證碼
使用kaptcha外掛來生成圖片驗證碼,匯入如下依賴
<dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>
kaptcha的一些配置
@Component public class KaptchaConfig { @Bean public DefaultKaptcha getDefaultKaptcha(){ com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha(); Properties properties = new Properties(); properties.setProperty("kaptcha.border", "yes"); // 是否有邊框 properties.setProperty("kaptcha.border.color", "105,179,90"); // 驗證碼邊框顏色 // properties.setProperty("kaptcha.textproducer.char.string", "ABCDEFG23456789"); // 驗證碼,不設定預設也存在 properties.setProperty("kaptcha.noise.color", "red"); // 干擾線的顏色 properties.setProperty("kaptcha.textproducer.font.color", "blue"); // 字型顏色 properties.setProperty("kaptcha.image.width", "110"); properties.setProperty("kaptcha.image.height", "40"); properties.setProperty("kaptcha.textproducer.font.size", "30"); properties.setProperty("kaptcha.session.key", "code"); properties.setProperty("kaptcha.textproducer.char.length", "4"); properties.setProperty("kaptcha.textproducer.font.names", "宋體,楷體,微軟雅黑"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
kaptcha使用
DefaultKaptcha defaultKaptcha = new DefaultKaptcha ();
//生產驗證碼字串
String code = defaultKaptcha.createText();
//使用生產的驗證碼字串返回一個BufferedImage物件
BufferedImage image = defaultKaptcha.createImage(code);
省略圖片驗證碼的生成過程,主要有兩個步驟:
1. 使用DefaultKaptcha 類生成BufferedImage 物件,將BufferedImage封裝到ImageCode類中,ImageCode主要有三個屬性:
String code(驗證碼字串)、LocalDateTime expireTime(過期時間)、BufferedImage image(圖片)
2. 將生成的ImageCode存入Session中
圖片驗證碼過濾器
為了在某些地址使用圖片驗證碼,我們需要寫一個過濾器。
@Component
public class ImageCodeFilter extends OncePerRequestFilter implements InitializingBean {
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
private Set<String> urls = new HashSet<>();
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
// 這裡可以設定,哪些地址是需要圖片驗證碼進行驗證的
urls.add("/authentication/form"); // 登入地址
}
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
boolean action = false;
// 判斷請求地址是否需要圖片驗證碼
for (String url : urls) {
if (antPathMatcher.match(url, httpServletRequest.getRequestURI())) {
action = true;
break;
}
}
if (action) {
try {
// 驗證驗證碼是否正確
validate(httpServletRequest);
} catch (ImageCodeException e) {
// 驗證碼錯誤則丟擲異常
authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private void validate(HttpServletRequest request) {
ImageCode imageCodeSession = (ImageCode)request.getSession().getAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
String imageCodeRequest = request.getParameter("imageCode");
if (imageCodeRequest == null || imageCodeRequest.isEmpty()) {
throw new ImageCodeException("圖片驗證碼不能為空");
}
if (imageCodeSession == null) {
throw new ImageCodeException("驗證碼不存在");
}
if (imageCodeSession.isExpired()) {
request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
throw new ImageCodeException("驗證碼已過期");
}
if(!imageCodeRequest.equalsIgnoreCase(imageCodeSession.getCode())) {
throw new ImageCodeException("驗證碼錯誤");
}
request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
}
}
配置過濾器
將ImageCodeFilter過濾器設定在UsernamePasswordAuthenticationFilter之前
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserService myUserService;
@Autowired
private MyAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private MyAuthenticationFailHandler authenticationFailHandler;
@Autowired
private ImageCodeFilter imageCodeFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class) // 將ImageCodeFilter過濾器設定在UsernamePasswordAuthenticationFilter之前
.authorizeRequests()
.antMatchers("/authentication/*","/login/*","/code/*") // 不需要登入就可以訪問
.permitAll()
.antMatchers("/user/**").hasAnyRole("USER") // 需要具有ROLE_USER角色才能訪問
.antMatchers("/admin/**").hasAnyRole("ADMIN") // 需要具有ROLE_ADMIN角色才能訪問
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/authentication/login") // 訪問需要登入才能訪問的頁面,如果未登入,會跳轉到該地址來
.loginProcessingUrl("/authentication/form")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailHandler)
;
}
// 密碼加密方式
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 重寫方法,自定義使用者
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().withUser("lzc").password(new BCryptPasswordEncoder().encode("123456")).roles("ADMIN","USER");
// auth.inMemoryAuthentication().withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
auth.userDetailsService(myUserService); // 注入MyUserService,這樣SpringSecurity會呼叫裡面的loadUserByUsername(String s)
}
}
登入頁面
<form th:action="@{/authentication/form}" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Enter username">
</div>
<div class="form-group">
<label for="Password">Password:</label>
<input type="password" class="form-control" id="Password" name="password" placeholder="Enter password">
</div>
<div class="form-group">
<label for="imageCode">imageCode:</label>
<input type="text" class="form-control" id="imageCode" name="imageCode" placeholder="Enter imageCode">
<img src="/code/image">
</div>
<div class="form-group" th:if="${param.error}">
<p th:if="${session.SPRING_SECURITY_LAST_EXCEPTION}">
<p th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></p>
</p>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
程式碼地址 https://github.com/923226145/SpringSecurity/tree/master/chapter4