Cloud E随笔-后端_piece3--实现登录功能
目錄
- 一、spring security 簡介
- 1. 概要
- 2. Spring Security 的核心功能
- 3. Spring Security特點
- 二、 登錄功能實現
- 1. 添加pom依賴
- 2. 修改配置文件 -application.yml
- 3. 創建工具類
- 3.1 新建JWTToken工具類
- 3.2 Admin實現UserDetails類
- 3.3 添加公共返回對象
- 3.4 添加登錄相關對象AdminLoginParam
- 4. 登錄功能實現
- 4.1 LoginController編寫
- 4.2 IAdminService接口編寫
- 4.3 AdminServerImpl接口類的實現
- 5. Security配置
- 5.1 準備配置類
- 5.2 添加jwt 登錄授權攔截器
- 5.3 添加自定義未授權未登錄結果返回
- 三、 接口文檔Swagger2準備
- 1. 依賴添加
- 2. Swagger2配置
- 3. 登錄驗證
- 4. 添加驗證碼模塊
- 4.1 添加驗證碼依賴
- 4.2 添加驗證碼配置文件
- 4.3 驗證碼生成
- 4.4 修改登錄傳遞參數
- 5. 登錄成功
一、spring security 簡介
1. 概要
Spring Security是Spring家族中的一員,Security基于Spring框架,提供了一套Web應用安全性的完整解決方案。
2. Spring Security 的核心功能
主要包括:
- 認證 (你是誰)
- 授權 (你能干什么)
- 攻擊防護 (防止偽造身份)
其核心就是一組過濾器鏈,項目啟動后將會自動配置。最核心的就是 Basic Authentication Filter 用來認證用戶的身份,一個在spring security中一種過濾器處理一種認證方式。
3. Spring Security特點
- 和 Spring 無縫整合。
- 全面的權限控制。
- 專門為 Web 開發而設計。
- 舊版本不能脫離 Web 環境使用。
- 新版本對整個框架進行了分層抽取,分成了核心模塊和 Web 模塊。單獨引入核心模塊就可以脫離 Web 環境。
- 重量級(缺點)。
二、 登錄功能實現
項目中使用 Spring Security 框架實現登錄功能
1. 添加pom依賴
<!--security 依賴--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency> <!--JWT 依賴--> <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version> </dependency>2. 修改配置文件 -application.yml
# jwt配置 jwt:# JWT存儲的請求頭tokenHeader: Authorization# JWT 加解密使用的密鑰secret: cloude-secret# JWT的超期限時間(60*60*24)expiration: 604800# JWT 負載中拿到開頭tokenHead: Bearer3. 創建工具類
3.1 新建JWTToken工具類
創建config.security目錄,并且在其目錄新建JwtTokenUtil.java文件
package com.chuci.server.config.security;import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component;import java.util.Date; import java.util.HashMap; import java.util.Map;/*** @Auther chuci* @Data 2021-12-22 22:43* @Description:*/ @Component public class JwtTokenUtil {private static final String CLAIM_KEY_USERNAME = "sub"; //荷載 用戶名private static final String CLAIM_KEY_CREATED = "created"; //荷載 創建時間// 通過配置獲取@Value("${jwt.secret}")private String secret; //JWT密鑰@Value("${jwt.expiration}")private Long expiration; //JWT失效時間/*** 根據用戶信息生成token* @param userDetails* @return*/public String generateToken(UserDetails userDetails){Map<String, Object> claims = new HashMap<>();claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}/*** 從token中獲取用戶名* @param token* @return*/public String getUserNameFromToken(String token){String username;try {Claims claims = getClaimsFromToken(token); //根據token獲取荷載username = claims.getSubject();}catch (Exception e){username = null;e.printStackTrace();}return username;}/*** 判斷token是否有效* @param token* @param userDetails* @return*/public boolean validateToken(String token, UserDetails userDetails){String username = getUserNameFromToken(token); // 判斷username是否一致以及token是否失效return username.equals(userDetails.getUsername()) && !isTokenExpired(token);}/*** 驗證token是否可以被刷新* @param token* @return*/public boolean canRefresh(String token){return !isTokenExpired(token); //token過期就可以被刷新了}/*** 刷新token* @param token* @return*/public String refreshToken(String token){Claims claims = getClaimsFromToken(token);claims.put(CLAIM_KEY_CREATED, new Date()); //更新創建時間 達到刷新token的目的return generateToken(claims);}/*** 判斷token是否失效* @param token* @return*/private boolean isTokenExpired(String token) {Date expiredDate = getExpiredDateFromToken(token);return expiredDate.before(new Date());}/*** 從token中獲取時間* @param token* @return*/private Date getExpiredDateFromToken(String token) {Claims claims = getClaimsFromToken(token);return claims.getExpiration();}/*** 從token中獲取荷載* @param token* @return*/private Claims getClaimsFromToken(String token) {Claims claims = null;try {claims = Jwts.parser() //轉荷載.setSigningKey(secret) //添加簽名.parseClaimsJws(token) //添加密鑰.getBody(); //拿到荷載}catch (Exception e){e.printStackTrace();}return claims;}/*** 根據JWT生成token 私有 只需public String generateToken調用* @param claims* @return*/private String generateToken(Map<String, Object> claims){return Jwts.builder() //jwts生成.setClaims(claims) //荷載.setExpiration(generateExporation()) //失效時間.signWith(SignatureAlgorithm.HS512, secret) //簽名.compact(); //密鑰}/*** 生成token失效時間* @return*/private Date generateExporation() { // 系統當前時間 加失效時間return new Date(System.currentTimeMillis() + expiration * 1000);} }3.2 Admin實現UserDetails類
實現UserDetails,重寫其方法,將所有返回類型改為 true,但注意isEnabled()方法是否啟用賬號,返回值返回Admin類中enable屬性值,因此isEnabled()方法返回值為enabled。
3.3 添加公共返回對象
每次請求,為了前后端參數統一,規定公共返回對象SysResult
創建vo目錄,并且新建SysResult.java。包含狀態碼code, 返回信息message以及返回對象data。并創建success方法以及error方法。同是使用lombok生成無參全參構造方法以及get/set方法。
注:返回對象可返回任何對象類型。
3.4 添加登錄相關對象AdminLoginParam
創建目錄結構bean,并且新建文件AdminLoginParam.java文件用來登錄時參數傳遞。若使用Admin實體進行登錄實體對象,則傳遞的對象太大,所以在這里進行簡化,暫時僅傳遞 用戶名以及密碼即可(后期還需要傳遞驗證碼)。
package com.chuci.server.bean;import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;/*** 用戶登錄實體類** @Auther chuci* @Data 2022-01-10 15:08* @Description:*/@Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "AdminLogin實體類", description = "") public class AdminLoginParam {@ApiModelProperty(value = "用戶名", required = true)private String username;@ApiModelProperty(value = "密碼", required = true)private String password;}4. 登錄功能實現
4.1 LoginController編寫
新建文件LoginController.java并進行登錄代碼控制層的編寫,主要實現:
4.2 IAdminService接口編寫
由MybatisPlus代碼生成器生成的所有Service接口,前綴均有“I”標注,即Admin實體的Service接口為IAdminService。
主要實現登錄以及登錄用戶信息的查詢。
4.3 AdminServerImpl接口類的實現
package com.chuci.server.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.chuci.server.config.security.JwtTokenUtil; import com.chuci.server.entity.Admin; import com.chuci.server.mapper.AdminMapper; import com.chuci.server.service.IAdminService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.chuci.server.vo.SysResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map;/*** <p>* 服務實現類* </p>** @author chuci* @since 2021-12-22*/ @Service public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate AdminMapper adminMapper;@Value("${jwt.tokenHead}")private String tokenHead;/*** 登陸之后返回token** @param code* @param username* @param password* @param request* @return*/@Overridepublic SysResult login(String username, String password, String code, HttpServletRequest request) { // 登錄UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (userDetails == null || passwordEncoder.matches(password,userDetails.getPassword())){return SysResult.error("用戶密碼不正確");}if(!userDetails.isEnabled()){return SysResult.error("賬號禁用,請聯系管理員!");}// 更新登錄用戶對象UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);// 生成tokenString token = jwtTokenUtil.generateToken(userDetails);Map<String, String> tokenMap = new HashMap<>();tokenMap.put("token", token);tokenMap.put("tokenHead", tokenHead);return SysResult.success("登錄成功", tokenMap);}/*** 根據用戶名獲取用戶信息* @param username* @return*/@Overridepublic Admin getAdminByUserName(String username) {return adminMapper.selectOne(new QueryWrapper<Admin>().eq("username", username).eq("enabled", true));} }5. Security配置
5.1 準備配置類
新建文件 SecurityConfig.java
SpringSecurity的登錄邏輯是通過UserDetailsService中的loadByUserName來實現的。重寫userDetailsService()方法并重新實現configure(AuthenticationManagerBuilder auth)。configure(WebSecurity web)開放部分接口以及資源。configure(HttpSecurity http)使用JWT, 不需要csrf。
5.2 添加jwt 登錄授權攔截器
新建JWTAuthencationTokenFilter.java 文件
package com.chuci.server.config.security;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;/*** @Auther chuci* @Data 2022-01-10 22:59* @Description: JWT 登錄授權過濾器*/public class JWTAuthencationTokenFilter extends OncePerRequestFilter {@Value("${jwt.tokenHeader}")private String tokenHeader;@Value("${jwt.tokenHead}")private String tokenHead;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String authHeader = request.getHeader(tokenHeader); // 存在tokenif(authHeader != null && authHeader.startsWith(tokenHead)){String authToken = authHeader.substring(tokenHead.length());String username = jwtTokenUtil.getUserNameFromToken(authToken); // token存在用戶但未登錄if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 驗證token是否有效,重新設置用戶對象if (jwtTokenUtil.validateToken(authToken, userDetails)){UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);}}}filterChain.doFilter(request, response);} }5.3 添加自定義未授權未登錄結果返回
三、 接口文檔Swagger2準備
在團隊開發中,一個好的 API 文檔不但可以減少大量的溝通成本,還可以幫助一位新人快速上手業務。傳統的做法是由開發人員創建一份 RESTful API 文檔來記錄所有的接口細節,并在程序員之間代代相傳。
這種做法存在以下幾個問題:
-
API 接口眾多,細節復雜,需要考慮不同的HTTP請求類型、HTTP頭部信息、HTTP請求內容等,想要高質量的完成這份文檔需要耗費大量的精力;
-
難以維護。隨著需求的變更和項目的優化、推進,接口的細節在不斷地演變,接口描述文檔也需要同步修訂,可是文檔和代碼處于兩個不同的媒介,除非有嚴格的管理機制,否則很容易出現文檔、接口不一致的情況
Swagger2 的出現就是為了從根本上解決上述問題。它作為一個規范和完整的框架,可以用于生成、描述、調用和可視化 RESTful 風格的 Web 服務:
接口文檔在線自動生成,文檔隨接口變動實時更新,節省維護成本
支持在線接口測試,不依賴第三方工具
1. 依賴添加
Swagger2自帶UI不太好看,之后更換第三方UI界面
<!-- swagger2 依賴 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!-- Swagger第三方ui依賴 --> <!-- 太丑了,顏色太亮--> <!-- <dependency>--> <!-- <groupId>com.github.xiaoymin</groupId>--> <!-- <artifactId>swagger-bootstrap-ui</artifactId>--> <!-- <version>1.9.6</version>--> <!-- </dependency>--><!--解決集成knife4j時沖突問題--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.0.1-jre</version></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-micro-spring-boot-starter</artifactId><version>2.0.5</version></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.5</version></dependency>2. Swagger2配置
在config文件夾下創建新文件夾swagger存放Swagger2配置類。新建SwaggerConfig.java文件
在這里進行簡單常用配置
同時為了測試,我們新建了HelloController。java文件進行Swagger的測試
我們通過域名:端口號/doc,html來訪問swagger
3. 登錄驗證
未登錄狀態下去請求接口:
提示未登錄,需要登錄。
4. 添加驗證碼模塊
4.1 添加驗證碼依賴
這里采用谷歌的驗證碼解決方案
</dependency><!-- google kaptcha依賴 --><dependency><groupId>com.github.axet</groupId><artifactId>kaptcha</artifactId><version>0.0.9</version></dependency>4.2 添加驗證碼配置文件
新建CaptchaConfig配置文件,在這里進行驗證碼的一些設置,如邊框,字體大小,字體樣式等等。
4.3 驗證碼生成
package com.chuci.server.controller;import com.google.code.kaptcha.impl.DefaultKaptcha; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.IOException;/*** 驗證碼** @Auther chuci* @Data 2022-01-15 18:14* @Description:*/ @RestController public class CaptchaController {@Autowiredprivate DefaultKaptcha defaultKaptcha;@ApiOperation(value = "驗證碼")@GetMapping(value = "/captcha", produces = "image/jpeg")public void captcha(HttpServletRequest request, HttpServletResponse response){// 定義response輸出類型為image/jpeg類型response.setDateHeader("Expires", 0);// Set standard HTTP/1.1 no-cache headers.response.setHeader("Cache-Control", "no-store, no-cache, mustrevalidate");// Set IE extended HTTP/1.1 no-cache headers (use addHeader).response.addHeader("Cache-Control", "post-check=0, pre-check=0");// Set standard HTTP/1.0 no-cache header.response.setHeader("Pragma", "no-cache");// return a jpegresponse.setContentType("image/jpeg");//-------------------生成驗證碼 begin --------------------------//獲取驗證碼文本內容String text = defaultKaptcha.createText();System.out.println("驗證碼:" + text);//將驗證碼放在session中request.getSession().setAttribute("captcha", text);//根據文本內容創建圖片驗證碼BufferedImage image = defaultKaptcha.createImage(text);ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();//輸出流輸出圖片,格式jpgImageIO.write(image, "jpg", outputStream);outputStream.flush();} catch (IOException e) {e.printStackTrace();}finally {if(outputStream != null){try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}}//-------------------生成驗證碼 end --------------------------} }重啟項目,我們可以看到swagger出現驗證碼模塊,并且測試成功生成驗證碼
4.4 修改登錄傳遞參數
同時修改Service層文件以及實現Imp層文件,并進行驗證碼正確性的檢測
5. 登錄成功
再次進行接口調用,則會帶如剛剛的參數值token令牌進行登錄驗證。
致此,整個登錄模塊已完成,等待前端頁面調用驗證即可。
至此,本節完~~~
上一節: Cloud E隨筆-后端_piece2–代碼生成器
下一節:
此系列以完整記錄自己項目經歷此系列以完整記錄自己項目經歷 此系列以完整記錄自己項目經歷
總結
以上是生活随笔為你收集整理的Cloud E随笔-后端_piece3--实现登录功能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对浏览器的基本认知
- 下一篇: 高中计算机应用面试教资真题,2019下半