目录

  • 一、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 开发而设计。
    1. 旧版本不能脱离 Web 环境使用。
    2. 新版本对整个框架进行了分层抽取,分成了核心模块和 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: Bearer

3. 创建工具类

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

package com.chuci.server.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Collection;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;/*** <p>* * </p>** @author chuci* @since 2021-12-22*/
@TableName("t_admin")
@ApiModel(value = "Admin对象", description = "")
public class Admin implements Serializable, UserDetails {private static final long serialVersionUID = 1L;@ApiModelProperty("id")@TableId(value = "id", type = IdType.AUTO)private Integer id;@ApiModelProperty("姓名")private String name;@ApiModelProperty("手机号码")private String phone;@ApiModelProperty("住宅电话")private String telephone;@ApiModelProperty("联系地址")private String address;@ApiModelProperty("是否启用")private Boolean enabled;@ApiModelProperty("用户名")private String username;@ApiModelProperty("密码")private String password;@ApiModelProperty("用户头像")private String userFace;@ApiModelProperty("备注")private String remark;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getTelephone() {return telephone;}public void setTelephone(String telephone) {this.telephone = telephone;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public Boolean getEnabled() {return enabled;}public void setEnabled(Boolean enabled) {this.enabled = enabled;}public String getUsername() {return username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUserFace() {return userFace;}public void setUserFace(String userFace) {this.userFace = userFace;}public String getRemark() {return remark;}public void setRemark(String remark) {this.remark = remark;}/*** 重写 实现 UserDetails* @return*/@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return enabled;    //是否启用}public void setUsername(String username) {this.username = username;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** 重写toString* @return*/@Overridepublic String toString() {return "Admin{" +"id=" + id +", name=" + name +", phone=" + phone +", telephone=" + telephone +", address=" + address +", enabled=" + enabled +", username=" + username +", password=" + password +", userFace=" + userFace +", remark=" + remark +"}";}
}

3.3 添加公共返回对象

每次请求,为了前后端参数统一,规定公共返回对象SysResult
创建vo目录,并且新建SysResult.java。包含状态码code, 返回信息message以及返回对象data。并创建success方法以及error方法。同是使用lombok生成无参全参构造方法以及get/set方法。
注:返回对象可返回任何对象类型。

package com.chuci.server.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 公共返回对象** @Auther chuci* @Data 2022-01-09 22:26* @Description:*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysResult {private long code;              //状态码private String message;         //返回信息private Object data;            //返回对象/*** 请求成功返回对象* @return*/public static SysResult success(){return new SysResult(200, "服务请求成功", null);}public static SysResult success(String msg){return new SysResult(200, msg, null);}public static SysResult success(String msg, Object data){return new SysResult(200, msg, data);}/*** 请求失败* @return*/public static SysResult error(){return new SysResult(500, "服务请求失败", null);}public static SysResult error(String msg){return new SysResult(500, msg, null);}public static SysResult error(String msg, Object data){return new SysResult(500, msg, data);}
}

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并进行登录代码控制层的编写,主要实现:

  1. 用户登录
  2. 登录用户信息获取
  3. 退出登录
package com.chuci.server.controller;import com.chuci.server.entity.Admin;
import com.chuci.server.service.IAdminService;
import com.chuci.server.bean.AdminLoginParam;
import com.chuci.server.vo.SysResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.security.Principal;/*** 登录** @Auther chuci* @Data 2022-01-10 15:12* @Description:*/
@Api(tags = "LoginController")
@RestController
public class LoginController {@Autowiredprivate IAdminService adminService;/*** 进行登录操作,返回SysResult对象。data包含登录所需验证* @param adminLoginParam* @param request* @return*/@ApiOperation(value = "登陆之后返回token")@PostMapping("/login")public SysResult login(@RequestBody AdminLoginParam adminLoginParam, HttpServletRequest request){return adminService.login(adminLoginParam.getUsername(), adminLoginParam.getPassword(), adminLoginParam.getCode(), request);}/*** 登录成功,获取当前登录对象所有信息* @param principal* @return*/@ApiOperation(value = "获取当前登录用户信息")@GetMapping("/admin/info")public Admin getAdminInfo(Principal principal){if (principal == null){return null;}String username = principal.getName();Admin admin = adminService.getAdminByUserName(username);admin.setPassword(null);return admin;}/*** 退出登录,前端删除token进行退出操作* @return*/@ApiOperation(value = "退出登录")@PostMapping("/logout")public SysResult logout(){return SysResult.success("注销成功");}}

4.2 IAdminService接口编写

由MybatisPlus代码生成器生成的所有Service接口,前缀均有“I”标注,即Admin实体的Service接口为IAdminService。
主要实现登录以及登录用户信息的查询。

public interface IAdminService extends IService<Admin> {/*** 登录之后返回token** @param code* @param username* @param password* @param request* @return*/SysResult login(String username, String password, HttpServletRequest request);/*** 根据用户名获取用户信息* @param username* @return*/Admin getAdminByUserName(String username);
}

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。

package com.chuci.server.config.security;import com.chuci.server.entity.Admin;
import com.chuci.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** @Auther chuci* @Data 2022-01-10 22:34* @Description: Security配置类*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate IAdminService adminService;@Autowiredprivate RestfulAccessDeniedHandler restfulAccessDeniedHandler;@Autowiredprivate RestAuthorizationEntryPoint restAuthorizationEntryPoint;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}/*** 开放以下接口网页资源不进行安全认证* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/login","/logout","/captcha","css/**","js/**","/webjars/**","/swagger-resources/**","/v2/api-docs/**","/index.html","/doc.html","favicon.ico");}@Overrideprotected void configure(HttpSecurity http) throws Exception {//        使用JWT, 不需要csrfhttp.csrf().disable()
//                基于token, 不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
//               所有请求都需要认证.anyRequest().authenticated().and().headers().cacheControl();
//        添加jwt 登录授权拦截器http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//        添加自定义未授权未登录结果返回http.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).authenticationEntryPoint(restAuthorizationEntryPoint);}@Override@Beanpublic UserDetailsService userDetailsService() {return username -> {Admin admin = adminService.getAdminByUserName(username);if (admin != null) {return admin;}return null;};}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic JWTAuthencationTokenFilter jwtAuthencationTokenFilter() {return new JWTAuthencationTokenFilter();}
}

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 添加自定义未授权未登录结果返回

  1. 未授权结果返回
package com.chuci.server.config.security;import com.chuci.server.vo.SysResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;/*** @Auther chuci* @Data 2022-01-14 22:06* @Description:*/@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {httpServletResponse.setCharacterEncoding("UTF-8");httpServletResponse.setContentType("application/json");PrintWriter writer = httpServletResponse.getWriter();SysResult result = SysResult.error("权限不足,请联系管理员");result.setCode(403);writer.write(new ObjectMapper().writeValueAsString(result));writer.flush();writer.close();}
}
  1. 未登录结果返回
package com.chuci.server.config.security;import com.chuci.server.vo.SysResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;/*** @Auther chuci* @Data 2022-01-14 21:42* @Description:*/
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {httpServletResponse.setCharacterEncoding("UTF-8");  //设置编码格式httpServletResponse.setContentType("application/json"); //设置传输为JSON格式PrintWriter writer = httpServletResponse.getWriter();SysResult result = SysResult.error("尚未登录,请登录后再试!");result.setCode(401);writer.write(new ObjectMapper().writeValueAsString(result));writer.flush();writer.close();}
}

三、 接口文档Swagger2准备

在团队开发中,一个好的 API 文档不但可以减少大量的沟通成本,还可以帮助一位新人快速上手业务。传统的做法是由开发人员创建一份 RESTful API 文档来记录所有的接口细节,并在程序员之间代代相传。

这种做法存在以下几个问题:

  • API 接口众多,细节复杂,需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等,想要高质量的完成这份文档需要耗费大量的精力;

  • 难以维护。随着需求的变更和项目的优化、推进,接口的细节在不断地演变,接口描述文档也需要同步修订,可是文档和代码处于两个不同的媒介,除非有严格的管理机制,否则很容易出现文档、接口不一致的情况

Swagger2 的出现就是为了从根本上解决上述问题。它作为一个规范和完整的框架,可以用于生成、描述、调用和可视化 RESTful 风格的 Web 服务:

  1. 接口文档在线自动生成,文档随接口变动实时更新,节省维护成本

  2. 支持在线接口测试,不依赖第三方工具

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文件

在这里进行简单常用配置

package com.chuci.server.config.swagger;import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.ArrayList;
import java.util.List;/*** @Auther chuci* @Data 2022-01-14 22:22* @Description:*/
@Configuration
@EnableSwagger2     //启用Swagger2
@EnableKnife4j      //启用Knife4j
public class SwaggerConfig {/*** 通过@Configuration注解,让Spring来加载该类配置。* 再通过@EnableSwagger2注解来启用Swagger2。* <p>* 再通过createRestApi()函数创建Docket的Bean之后,* apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。* select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现* 采用扫描所有定义,Swagger会扫描所有Controller定义的API,并产生文档内容(除了被@ApiIgnore指定的请求)。** @return*/@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2)      //标明文档类型 Swagger2.apiInfo(apiInfo())      //apiInfo()用来创建Api的基本信息(这些信息会展现在文档页面中).groupName("CloudE_Server")     //组名.select()   //select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现.apis(RequestHandlerSelectors.basePackage("com.chuci.server.controller"))       //扫描特定包下面的文件  还有.any 扫描所有包.paths(PathSelectors.any())     //Swagger会扫描该包下的所有Controller定义的API,并产生文档内容(除了被@ApiIgnore定义的请求).build().securityContexts(securityContexts()).securitySchemes(securitySchemes());}private List<? extends SecurityScheme> securitySchemes() {//        设置请求头信息List<ApiKey> res = new ArrayList<>();ApiKey apiKey = new ApiKey("Authorization", "Authorization", "Header");res.add(apiKey);return res;}private List<SecurityContext> securityContexts() {//        设置需要认证的路径List<SecurityContext> res = new ArrayList<>();res.add(getContextByPath("/hello/.*"));return res;}private SecurityContext getContextByPath(String pathRegex) {return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex(pathRegex)).build();}private List<SecurityReference> defaultAuth() {List<SecurityReference> res = new ArrayList<>();AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];authorizationScopes[0] = authorizationScope;res.add(new SecurityReference("Authorization", authorizationScopes));return res;}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("CloudE 接口文档").description("物华天宝 , 龙光射牛斗之墟 \r"+ "人杰地灵 , 徐孺下陈蕃之榻\r"+ "------CloudE 接口文档").termsOfServiceUrl("www.baidu.com").contact(new Contact("【楚辞】", "http://localhost:8081/doc.html", "自己邮箱地址")).version("1.0").build();}
}

同时为了测试,我们新建了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配置文件,在这里进行验证码的一些设置,如边框,字体大小,字体样式等等。

package com.chuci.server.config.captcha;import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Properties;/*** 验证码配置类** @Auther chuci* @Data 2022-01-15 18:04* @Description:*/
@Configuration
public class CaptchaConfig {@Beanpublic DefaultKaptcha defaultKaptcha() {//验证码生成器DefaultKaptcha defaultKaptcha = new DefaultKaptcha();//配置Properties properties = new Properties();//是否有边框properties.setProperty("kaptcha.border", "yes");//设置边框颜色properties.setProperty("kaptcha.border.color", "105,179,90");//边框粗细度,默认为1// properties.setProperty("kaptcha.border.thickness","1");//验证码properties.setProperty("kaptcha.session.key", "code");//验证码文本字符颜色 默认为黑色properties.setProperty("kaptcha.textproducer.font.color", "blue");//设置字体样式properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");//字体大小,默认40properties.setProperty("kaptcha.textproducer.font.size", "30");//验证码文本字符内容范围 默认为abced2345678gfynmnpwx// properties.setProperty("kaptcha.textproducer.char.string", "");//字符长度,默认为5properties.setProperty("kaptcha.textproducer.char.length", "4");//字符间距 默认为2properties.setProperty("kaptcha.textproducer.char.space", "4");//验证码图片宽度 默认为200properties.setProperty("kaptcha.image.width", "100");//验证码图片高度 默认为40properties.setProperty("kaptcha.image.height", "40");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}}

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 修改登录传递参数

  1. 修改AdminLoginParam文件,新增验证码


  2. 修改登录Controller文件,传递参数新增验证码部分


    同时修改Service层文件以及实现Imp层文件,并进行验证码正确性的检测

//        验证码检测String captcha = request.getSession().getAttribute("captcha").toString();System.out.println("captcha:" + captcha + "; code:" + code);if(!StringUtils.hasLength(code) || !captcha.equalsIgnoreCase(code)){return SysResult.error("验证码错误,请重新输入!");}

5. 登录成功

  1. 验证码错误
  2. 登录成功
  3. 登录 成功,将返回的data信息中 tokenHead、token分别粘贴到swagger中Authorize页面参数值输入框中作为登录token令牌,tokenHead与token之间使用空格隔开
"tokenHead": "Bearer"
"token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2NDI2NjUwMzc4OTYsImV4cCI6MTY0MzI2OTgzN30.iVkznvdJ3DCCVSdVbCBN34nz_BG1JGXyfolJ5GLH7_uIZvEcMHLAzc6q8Hkzqx8AX_d7VEH_wk20mrbtk3HvgA"


再次进行接口调用,则会带如刚刚的参数值token令牌进行登录验证。



致此,整个登录模块已完成,等待前端页面调用验证即可。


至此,本节完~~~

上一节: Cloud E随笔-后端_piece2–代码生成器
下一节:

此系列以完整记录自己项目经历此系列以完整记录自己项目经历 此系列以完整记录自己项目经历

Cloud E随笔-后端_piece3--实现登录功能相关推荐

  1. 模板模式实现后端公众号登录功能,Java8版

    模板模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 父类:抽象类,将公共的方法写在父类,个性化的内容定义抽象方法由 ...

  2. un7.2:IDEA中实现登录功能

    我们无论是做网页还是小程序,都需要使用登录功能,那么我们该如何实现呢?接下来我用前后端分离的方式来给大家分享一些我的经验,希望可以帮到大家. 老规矩,后端工具:IDEA,前端:HbuilderX,浏览 ...

  3. sprongboot mysql登录注册_后端开发:SpringBoot实现注册与登录功能

    这次实现的注册与登录功能需要进行数据库的基本操作,而且是前后端分离式开发.总的来说就是首先进行数据库的设计,然后根据数据库进行编写服务端API接口,接着来到客户端或移动端,进行登录与注册的界面设计,接 ...

  4. Django前后端分离实现登录验证码功能

    Django前后端分离实现登录验证码功能 当下最流行最热门的开发方式当属前后端分离开发,分工也更加明确与专注,前端也是越来越难,几天不学习就跟不上节奏,一个月不学习可以好不夸张的说,你已经不适合这个行 ...

  5. 前后端实现网站登录注册功能

    实现网站登录注册: 这是网站的登录功能 登录后的页面效果是这样的 先分享一下我碰到的问题 登录时,跳转的页面不正确(也即输入用户名之后数据传不到后台的问题) 原因是在写UserDaoImpl类时,没有 ...

  6. 通过前后端交互实现简单注册登录功能

    文件夹路径图 code(总文件夹) public(HTML文件夹) cart.html(登录成功所跳转的页面) login.html(登录页面) register.html(组成页面) login.p ...

  7. 一步步带你做vue后台管理框架(三)——登录功能

    系列教程<一步步带你做vue后台管理框架>第三课 github地址:vue-framework-wz 线上体验地址:立即体验 <一步步带你做vue后台管理框架>第一课:介绍框架 ...

  8. 【Vue.js】vue用户登录功能

    之前用vue实现一个网站的登录功能,这里做一个记录和总结,以下是需要实现的登录业务描述: 1.输入用户名和密码,点击登录按钮,若两者匹配,即可进入首页,首页展示登录用户的信息: 2.当用户登录后,无法 ...

  9. (22)Ajax的基本使用(实现登录功能和局部刷新以及防止跨站请求伪造攻击)

    Ajax的作用 前后端分离的项目,需要交互,就要通过Ajax来完成交互 AJAX(Asynchronous Javascript And XML)翻译成中文就是"异步Javascript和X ...

  10. Spring Cloud Security:Oauth2实现单点登录

    摘要 Spring Cloud Security 为构建安全的SpringBoot应用提供了一系列解决方案,结合Oauth2可以实现单点登录功能,本文将对其单点登录用法进行详细介绍. 单点登录简介 单 ...

最新文章

  1. 如何利用单片机IO口产生两倍的电源电压
  2. mysql timestamp 晚8小时_mysql插入timeStamp类型数据时间相差8小时的解决办法
  3. ubuntu实现简单的划词工具
  4. 大众汽车和鸿蒙,鸿蒙系统下个月即将与大众见面,首发平台并非手机
  5. python 给类添加属性_python – 如何动态添加属性到类中?
  6. python hashlib安装_Hashlib加密,内置函数,安装操作数据库
  7. js刷新页面有哪几种方法
  8. USB控制相关批处理
  9. 骗访问量的机房人物列传by xMinh
  10. HTML5期末大作业:漫画网站设计——海贼王基地(5页) 学生动漫网页设计模板下载 海贼王大学生HTML网页制作作品 简单漫画网页设计成品 dreamweaver学生网站模板
  11. iOS-APP 签名原理
  12. 坎坷的微信小程序【笔记】
  13. 5.2 中心极限定理
  14. 纵观大型网站架构发展,总结持久化部分需要应对的问题
  15. 通信原理-第9章-数字信号的最佳接收
  16. workflow的简介
  17. IDEA 更新到 2021.2.3 咋样?【2021.3、2021.3.1看评论区】
  18. 手把手教你读财报----银行业---第九课
  19. 虚拟机搭建svn服务器,轻松搭建一台Windows SVN服务器
  20. 高保真Axure原型设计实战 - 自适应后台框架

热门文章

  1. abb机器人指令手册_ABB机器人CCLink 配置
  2. Red Giant 安装及爆炸效果详解
  3. Vistual Studio中x86和x64的区别
  4. 【半年时光-追寻你的足迹】
  5. matlab二维插值绘制地貌图
  6. 任务管理器被管理员停用怎么办
  7. JavaFX源码分析和实战之音频播放:MediaPlayer和AudioClip播放音频剪辑以及AudioClip与MediaPlayer的区别
  8. STM32按键总结(低电平有效及上升沿有效)
  9. 手机H5-调用百度地图导航
  10. 关于我的一些学习感悟