只寫如何應用,並不深入流程原理,如果想深入學習原理機制的只能失望了;
springSecurity5.0開始默認必須對密碼加密,之前的是可選;
0.準備工作,創建初始化的springBoot項目,版本是2.2.1
- IDEA創建springBoot項目,勾選web,以來如下
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
- 創建一個controller類體提供一個訪問接口
@RestController @RequestMapping("/admin") public class SecurityController { @RequestMapping("/hello") public String hello() { return "Hello World"; } }
- application.yml配置文件
server: port: 8080 tomcat: uri-encoding: utf-8 servlet: context-path: /security
一切準備就緒!!!
1.springBoot應用springSecurity只需一步!
得便於springBoot自動化配置,我們只需要在項目添加springSecurity的依賴就能將它運用在項目中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
再次運行你的項目訪問接口你會發現:
需要登陸才能訪問,如果你沒有自己配置用戶數據來源,springSecurity默認提供user用戶,密碼你可以在控制檯找到
然後登陸就會自動跳轉到受保護的接口:
2.security的配置,繼承WebSecurityConfigurerAdapter類重寫配置方法
security幾乎所有配置其中配置,重寫WebSecurityConfigurerAdapter的三個方法(其實是一個方法的重載,參數不一樣而已)
- configure(AuthenticationManagerBuilder auth):配置用戶數據來源,如果沒有重寫該方法默認提供user用戶以及動態生成的密碼
- void configure(HttpSecurity http):配置security的流程及工作組件,如處理器,過濾器,remenber-me等等
- configure(WebSecurity web):配置security作用的範圍,保護那些接口等等
3.自定義登陸界面
- 寫好自己的登陸界面,靜態資源如css、js、html、圖片等放在resources的static文件夾中即可
- 表單用戶名必須爲username(springSecurity默認,可以修改),密碼必須爲password(同用戶名)
- 表單的action可以隨意,但必須與springSecurity配置類配置登陸處理請求的url一致
- 配置springSecurity的配置類,繼承重寫WebSecurityConfigurerAdapter類的方法
-
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 定義哪些URL需要被保護、哪些不需要被保護 .anyRequest() // 任何請求,登錄後可以訪問 .authenticated() .and() .formLogin() .usernameParameter("username")//與登陸頁面一致 .passwordParameter("password")//與登陸頁面一致 .loginPage("/login.html")//自定義登陸頁面 .permitAll()//設置爲允許所有人訪問 .loginProcessingUrl("/securityLogin")//登陸處理請求路徑與action一致即可,不需要我們實現 .permitAll()//設置爲允許所有人訪問 .and() .logout()//登出配置 .logoutUrl("/logout")//不需要我們實現,訪問這個自動登出 .invalidateHttpSession(true) .deleteCookies("JSESSIONID") .and() .httpBasic() .disable() .csrf() .disable(); } /* *過濾不需要受保護的資源 */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers("/js/**") .antMatchers("/css/**") .antMatchers("/image/**"); } }
訪問接口,自動跳轉到自定義登陸頁面,輸入user、控制檯密碼
成功跳轉到訪問的接口
4.定義登陸成功、失敗處理器
當我們自定義登陸頁面後,會發現當我們登陸失敗會重新跳回登陸頁面,而沒有任何提示,而且我們很多時候都希望前後端通過json傳遞數據;
- 自定義登陸成功處理器,返回json信息,實現AuthenticationSuccessHandler接口並實現
//登陸賬戶密碼驗證成功處理器,返回一個json對象,用於異步處理 @Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private ObjectMapper objectMapper = new ObjectMapper(); /** * @param httpServletRequest * @param httpServletResponse * @param authentication 認證成功封裝的用戶信息 * @throws IOException */ @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException { //httpServletRequest.getSession().removeAttribute("checkCode"); // User user = (User) authentication.getPrincipal(); //更新最後一次登錄時間 String json = objectMapper.writeValueAsString(authentication.getPrincipal()); // System.out.println(json); httpServletResponse.setContentType("application/json;charset=utf-8"); httpServletResponse.getWriter().write(json); } }
1.獲取處理器的bean
2.配置登陸成功處理器
修改自己登陸的方式(前端),使用異步方式的表單登陸//ajax異步請求進行登陸驗證 function ajaxLogin() { $.post("/security/securityLogin", $("#loginForm").serialize(), function (data) { console.log(data); }, "json"); return false;//禁止表單提交再次登錄 }
重新啓動登陸:
可以看到控制檯已經打印springSecurity封裝的用戶信息,登陸成功處理器配置成功 -
自定義登陸失敗處理器,與成功處理器類似,就不多講,直接代碼
@Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { /** * @param httpServletRequest * @param httpServletResponse * @throws IOException * @throws ServletException */ @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException { System.out.println(e.getClass()+e.getMessage()); httpServletResponse.setContentType("application/json;charset=utf-8"); httpServletResponse.getWriter().write("{\"message\":\""+e.getMessage()+"\"}"); } }
神奇的是異常信息居然是中文,查了好久才發現是security國際化,之前使用是不會自動轉化爲中文的
自定義登陸失敗處理器完成
5.添加驗證碼驗證
1.實現提供驗證碼的api,並在配置類放行允許所有人訪問
@Controller
public class CheckCodeController {
private final Random r = new Random();
// 隨機字符集合中不包括0和o,O,1和l,因爲這些不易區分
private final String codes = "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYXZ";
/**
* 返回一張圖片
*
* @param response
* @param session
* @throws java.io.UnsupportedEncodingException
*/
@RequestMapping("/getCheckCode")
public void getCheckCode(HttpServletResponse response, HttpSession session) throws IOException {
//request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 設置不緩存圖片
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "No-cache");
response.setDateHeader("Expires", 0);
// 指定生成的響應圖片,一定不能缺少這句話,否則錯誤.
response.setContentType("image/jpeg");
int width = 80, height = 35; // 指定生成驗證碼的寬度和高度
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 創建BufferedImage對象,其作用相當於一圖片
Graphics g = image.getGraphics(); // 創建Graphics對象,其作用相當於畫筆
Graphics2D g2d = (Graphics2D) g; // 創建Grapchics2D對象
Random random = new Random();
Font mfont = new Font("楷體", Font.BOLD, 16); // 定義字體樣式
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height); // 繪製背景
g.setFont(mfont); // 設置字體
g.setColor(getRandColor(180, 200));
// 繪製100條顏色和位置全部爲隨機產生的線條,該線條爲2f
for (int i = 0; i < 100; i++) {
int x = random.nextInt(width - 1);
int y = random.nextInt(height - 1);
int x1 = random.nextInt(6) + 1;
int y1 = random.nextInt(12) + 1;
BasicStroke bs = new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); // 定製線條樣式
Line2D line = new Line2D.Double(x, y, x + x1, y + y1);
g2d.setStroke(bs);
g2d.draw(line); // 繪製直線
}
//用來保存驗證碼字符串文本內容
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; ++i) {// 隨機生成4個字符
String sTemp = String.valueOf(randomChar());
sb.append(sTemp);
Color color = new Color(20 + random.nextInt(110), 20 + random.nextInt(110), random.nextInt(110));
g.setColor(color);
// 將生成的隨機數進行隨機縮放並旋轉制定角度 PS.建議不要對文字進行縮放與旋轉,因爲這樣圖片可能不正常顯示
/* 將文字旋轉制定角度 */
Graphics2D g2d_word = (Graphics2D) g;
AffineTransform trans = new AffineTransform();
trans.rotate((45) * 3.14 / 180, 15 * i + 8, 7);
/* 縮放文字 */
//float scaleSize = random.nextFloat() + 0.8f;
//if (scaleSize > 1f)
float scaleSize = 1.4f;
trans.scale(scaleSize, scaleSize);
g2d_word.setTransform(trans);
g.drawString(sTemp, 15 * i + 5, 14);
}
session.setAttribute("checkCode", sb.toString());
System.out.println("checkCode: " + session.getAttribute("checkCode"));
//System.out.println("ssRand="+sb.toString());
g.dispose(); // 釋放g所佔用的系統資源
ImageIO.write(image, "JPEG", response.getOutputStream()); // 輸出圖片
response.getOutputStream().flush();
}
/* 該方法主要作用是獲得隨機生成的顏色 */
public Color getRandColor(int s, int e) {
Random random = new Random();
if (s > 255) {
s = 255;
}
if (e > 255) {
e = 255;
}
int rr, g, b;
rr = s + random.nextInt(e - s); // 隨機生成RGB顏色中的r值
g = s + random.nextInt(e - s); // 隨機生成RGB顏色中的g值
b = s + random.nextInt(e - s); // 隨機生成RGB顏色中的b值
return new Color(rr, g, b);
}
/*
*隨機獲取一個字符
*/
private char randomChar() {
int index = r.nextInt(codes.length());
return codes.charAt(index);
}
}
2.實現自己的驗證碼過器鏈及自定義驗證碼異常並將驗證碼過濾器放在登陸的賬號密碼驗證器前面
- 自定義驗證碼異常,需繼承 AuthenticationException,用來拋出異常給前面自定義登陸失敗處理器
//驗證碼驗證錯誤拋出的異常類 public class CheckCodeException extends AuthenticationException { public CheckCodeException(String msg, Throwable t) { super(msg, t); } public CheckCodeException(String msg) { super(msg); } }
- 實現驗證碼過濾器,繼承OncePerRequestFilter並重寫doFilterInternal方法實現自己驗證流程
//驗證碼驗證過濾器,繼承OncePerRequestFilter,該過濾器作用於賬戶密碼過濾器前 @Component public class CheckCodeAuthenticationFilter extends OncePerRequestFilter { @Autowired private MyAuthenticationFailureHandler myAuthenticationFailureHandler; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //判斷當前請求是否是登陸請求 if (request.getRequestURI().contains("/securityLogin")) { //檢驗驗證碼 try { //用戶輸入的驗證碼 final String imageCode = request.getParameter("code"); //獲取系統生成的驗證碼 String key = (String) request.getSession().getAttribute("checkCode"); if (key == null || key == "") { throw new CheckCodeException("請勿重複登陸"); } if (!imageCode.trim().equalsIgnoreCase(key.trim())) { //拋出自定義驗證碼異常 throw new CheckCodeException("驗證碼錯誤"); } } catch (AuthenticationException e) { //交給authenticationFailureHandler(自定義登陸失敗處理器)處理 myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e); return; } } //驗證碼正確放行 filterChain.doFilter(request, response); } }
- 在配置類中將驗證碼過濾器放在security的過濾鏈中,並放在靠前面的,賬號密碼驗證之前
重啓項目
成功!!!
不知不覺很長的博客了,有收穫的點個贊,下次得閒寫下篇關於security其他配置包括:
- 自定義登陸數據來源,內存用戶數據以及數據庫(RBAC權限模型)
- remenber-me
- 自定義登出處理器
- 配置不允許重複登陸
- 權限不足處理器,未登錄處理器