前言:
而今姐妹们对“登陆验证c语言”大体比较重视,朋友们都需要知道一些“登陆验证c语言”的相关知识。那么小编同时在网上收集了一些对于“登陆验证c语言””的相关内容,希望小伙伴们能喜欢,兄弟们快快来学习一下吧!Spring Security + JWT + Swagger2 登录验证一套流程
主要是三个框架的集成配置,以及各个独立的配置(主要是 JWT + Security 的登录验证)。
流程:
构建 Spring Boot 基本项目,准备数据库表 User —— 用于存放登录实体类信息。配置 Security 和 Swagger2 环境,确保没有什么问题。构建 RespBean——公共返回实体类,JwtTokenUtil——JWT token 工具类,User——登录实体类让 User 实现 UserDetails 接口,重写部分方法。配置 Security 实现重写 UserDetailsService 方法,以及 PasswordEncoder——密码凭证器 并加上 @Bean 注解。这两个主要用于设置 Security 的认证。构建 jwtAuthenticationTokenFilter 类——自定义 JWT Token 拦截器,并在 SecurityConfig 的授权方法中添加此拦截器。在 Swagger2Config 配置类中,配置有关 Security 的 Token 认证。启动项目查看代码是否准确。1. 构建 Spring Boot 基本项目,准备数据库——User
项目子模块:authority-security,父模块已引入 Spring boot 依赖 2.3.0
1.1 导入依赖
<dependencies> <!-- web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombok 依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- mysql 依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- mybatis-plus 依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency> <!-- swagger2 依赖 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <!-- swagger 第三方 UI 依赖 --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> <!-- spring 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> <!-- commons-pool2 对象池依赖 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency></dependencies>复制代码
构建数据库表:user
create table user( id int primary key auto_increment, username varchar not null, password varchar not null, info varchar(200), enabled tinyint(1) default 1)insert into user values(default,"admin","$2a$10$Himwt.wu3MPOLnNQ9YUH8O2quxgi7bMuomiNeFsVKRay87.qG5dgy","管理员 info ...",default)复制代码
username:admin;password:123
配置 application.yml 文件参数:
server: port: 8082spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/dbtest16?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: admin password: admin hikari: # 连接池名字 pool-name: DateHikari # 最小空闲连接数 minimum-idle: 5 # 空闲连接存活最大事件,默认10分钟(600000) idle-timeout: 180000 # 最大连接数:默认 10 maximum-pool-size: 10 # 从连接池返回的连接自动提交 auto-commit: true # 连接最大存活时间,0 表示永久存活,默认 1800000(30 min) max-lifetime: 1800000 # 连接超时事件 30 s connection-timeout: 30000 # 测试连接是否可用的查询语句 connection-test-query: SELECT 1# MP 配置mybatis-plus: # 配置 Mapper 映射文件 mapper-locations: classpath*:/mapper/*Mapper.xml # 实体类的别名包 type-aliases-package: com.cnda.pojo configuration: # 自动驼峰命名 map-underscore-to-camel-case: false# MyBatis 的 SQL 打印是方法接口所在的包logging: level: com.cnda.mapper: debug# JWT 配置jwt: # JWT 存储的请求头 tokenHeader: Authorization # JWT 加密使用的密钥 secret: test-cnda-secret # JWT 的有效时间 (60*60*24) expiration: 604800 # JWT 负载中拿到开头 规定 tokenHead: Bearer复制代码
User 实体类代码:
@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private Integer id; private String username; private String password; private String info; private Boolean enabled;}复制代码2. 配置 Security 和 Swagger2 的配置
先配置好这两个确保没有什么问题,因为重点是 JWT,这两个配置比较简单,当搭配了 JWT 之后,Swagger2 也需要与两者集成一些配置,这个后面再说,现在只配置基本设置。
2.1 配置 SecurityConfig
@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { super.configure(auth); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/hello", // 下面是对静态资源以及 swagger2 UI 的放行。 "/css/**", "/js/**", "/img/**", "/index.html", "favicon.ico", "/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs/**", "/ws/**" ); } @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); }}复制代码
上面使用 WebSecurity 放行了 /hello 请求,在 LoginController 中。
@RestControllerpublic class LoginController { @RequestMapping("/hello") public String hello(){ return "Hello Word!"; }}复制代码
这意味除了 localhost:8082/hello 会被放行,其他请求都会被 Security 拦截重定向到 /login(这个请求 Security 内部已经实现了包括相关页面)。
2.2 配置 Swagger2Config
@Configuration@EnableSwagger2public class Swagger2Config { @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) // 配置 apiInfo .select() // 选择那些路径和api会生成document .apis(RequestHandlerSelectors.basePackage("com.cnda.controller")) // // 对哪些 api进行监控,RequestHandlerSelectors.basePackage 基于包扫描 .paths(PathSelectors.any()) // 对所有路径进行监控 .build(); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("在线接口文档") .description("在线接口文档") .contact(new Contact("cnda",";,"xxx@xxx.com")) .build(); }}复制代码
运行效果:
修改一下 Rustful 风格,并加了一个 /hello1 请求,不放行,打印内容相同。
可以看到 Security 和 Swagger2 基本配置完成。
3. 构建 JWT 工具类、公共响应对象
JWT 工具类主要用于生成 JWT,判断 JWT 是否有效,刷新 JWT 等方法。
公共响应对象——RespBean,返回的都已 JSON 格式返回。
3.1 JwtUtil
@Componentpublic class JwtUtil { // 准备两个存放在荷载的内容 private static final String CLAIM_KEY_SUB = "sub"; private static final String CLAIM_KEY_CREATE = "ibt"; // 提取 application.yml 中 JWT 的参数: // 1. expiration Long @Value("${jwt.expiration}") private Long expiration; // 2. secret String @Value("${jwt.secret}") private String secret; // 密钥 // 根据用户名构建 token public String foundJWT(UserDetails userDetails) { String username = userDetails.getUsername(); Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_SUB, username); claims.put(CLAIM_KEY_CREATE, new Date()); return foundJWT(claims); } // 根据荷载 map 构建 token private String foundJWT(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(getExpiration()) // 过期时间 .signWith(SignatureAlgorithm.HS512, secret) // 设置签名算法和密钥 .compact(); } // 判断 token 是否有效 public boolean validateToken(String token,UserDetails userDetails){ // 从 token 中获取 username 与 userDetails 中的username 对比 String username = getUsernameInToken(token); // 判断 username 是否一致以及 token 是否过期 return username.equals(userDetails.getUsername()) && !isExpired(token); } // 判断 token 是否过期 // true 过期 false 没过期 private boolean isExpired(String token) { Date expiration = getClaimsInToken(token).getExpiration(); return expiration.before(new Date()); } // 从 token 中提取荷载信息 public Claims getClaimsInToken(String token){ Claims claims = null; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); }catch (Exception e){ e.printStackTrace(); } return claims; } // 从 token 中提取用户名信息 public String getUsernameInToken(String token){ String username; try { username = getClaimsInToken(token).getSubject(); }catch (Exception e){ username = null; } return username; } // token 是否能刷新 public boolean tokenCanRef(String token){ return !isExpired(token); // 有效地 token 才能被刷新 } // 刷新 token public String refToken(String token){ Claims claimsInToken = getClaimsInToken(token); claimsInToken.put(CLAIM_KEY_CREATE,new Date()); return foundJWT(claimsInToken); } // 设置过期时间 private Date getExpiration() { return new Date(System.currentTimeMillis() + expiration * 1000); }}复制代码3.2 RespBean 公共返回对象
@Data@AllArgsConstructor@NoArgsConstructorpublic class RespBean { private long code; private String message; private Object obj; /** * 返回响应结果 */ private static RespBean result(long code, String message, Object obj) { return new RespBean(code, message, obj); } /* 返回成功响应 */ public static RespBean success(String message) { return result(200, message, null); } /* 返回成功响应以及数据体 */ public static RespBean success(String message, Object obj) { return result(200, message, obj); } /* 返回错误响应 */ public static RespBean error(String message) { return result(500, message, null); }}复制代码4. 让 User 实体类实现 UserDetails 的方法成为 Security 验证的用户核心主体
由于 Security 框架的性质,自定义授权和认证时,一般情况下会自定义 UserDetails。
@Data@AllArgsConstructor@NoArgsConstructorpublic class User implements UserDetails { private Integer id; private String username; private String password; private String info; private Boolean enabled; @Override public Collection<? extends GrantedAuthority> getAuthorities() { // 权限角色 return null; } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { // 这里数据库实现了该字段,直接用即可 return this.enabled; }}复制代码5. 重写 UserDetailsServer 和 PasswordEncoder5.1 重写 UserDetailsServer
这个类就只有一个方法:
loadUserByUsername(UserDetails details),该方法用于根据用户名加载用户信息,用作于 Security 的后续认证,同时也可以用一个类去实现该接口,这里为了方便,同时也是 Lambda 表达式。
注意:这里的 UserMapper 没有代码展示了,就一个根据用户名查询用户信息的 SQL。
@Resourceprivate UserMapper mapper;@Bean@Overrideprotected UserDetailsService userDetailsService() { return username -> { User user = mapper.find(username); if (user!=null){ return user; } throw new UsernameNotFoundException("用户名或密码不正确"); };}复制代码5.2 PasswordEncoder——密码凭证器
这个类主要用于验证表单提交的密码是否和 重写之后的 UserDetailsServer 得到的 UserDetails 中的加密密码一致。
@Beanpublic PasswordEncoder encoder(){ return new BCryptPasswordEncoder();}复制代码5.3 配置到 SecurityConfig 的认证中
@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()).passwordEncoder(encoder());}复制代码6. 配置 JWT 的拦截器
public class JwtTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Autowired private UserDetailsService service; @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { // 获取请求头中的指定的值 String headerToken = httpServletRequest.getHeader(tokenHeader); // 保证 header中的 token 不为 null,且以指定字串开头——Bearer if (headerToken!=null && headerToken.startsWith(tokenHead)){ // 截取有效 token String jwtToken = headerToken.substring(tokenHead.length()); String username = jwtUtil.getUsernameInToken(jwtToken); // 判断 UserDetails 中的用户主体是否为null if (username!=null && SecurityContextHolder.getContext().getAuthentication() == null){ // SecurityContextHolder.getContext().getAuthentication() == null 代表着此时 Security 中没有登录的用户主体 // 此时可以使用有效地 jwtToken 进行用户认证 UserDetails userDetails = service.loadUserByUsername(username); // 判断 token 是否有效 if (jwtUtil.validateToken(jwtToken,userDetails)){ // 如果有效则使用 token 中的信息进行登录 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null, userDetails.getAuthorities()); // 根据请求设置 Details,包含了部分请求信息和主体信息。具体效果不清楚...坑 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); // 将 authenticationToken 设置到 SecurityContext 中 SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } } filterChain.doFilter(httpServletRequest,httpServletResponse); }}复制代码6.1 将 JWT 拦截器设置到 SecurityConfig 的授权方法中。
@Overrideprotected void configure(HttpSecurity http) throws Exception { // 由于我们使用的是 JWT 令牌的形式来验证用户,所以可以将 csrf 防御关闭 // JWT 能有效防止 csrf 攻击,强行使用 csrf 可能导致令牌泄露 http.csrf() .disable() // 基于 token,不需要使用 Session 了 .sessionManagement() // Session 管理 // 管理 Session 创建策略 // ALWAYS, 总是创建HttpSession // NEVER, 只会在需要时创建一个HttpSession // IF_REQUIRED, 不会创建HttpSession,但如果它已经存在,将可以使用HttpSession // STATELESS; 永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 授权请求 // 除了上面的请求,其他所有请求都需要认证 .anyRequest() .authenticated() .and() // 禁止缓存 .headers() .cacheControl(); // 自定义拦截器 JWT 过滤器 http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); // 将过滤器按照一定顺序加入过滤器链。}@Beanpublic JwtTokenFilter jwtTokenFilter() { return new JwtTokenFilter();}复制代码7. 完善 LoginController 请求,运行项目。
LoginController
@RestControllerpublic class LoginController { @Autowired private UserService service; @GetMapping("/hello") public String hello(){ return "Hello Word!"; } @GetMapping("/hello1") public String hello1(){ return "Hello1 Word!"; } @PostMapping("/login") public RespBean loginUser(@RequestBody User user, HttpServletRequest request){ return service.login(user.getUsername(),user.getPassword(),request); }}复制代码
UserService,使用的时 MVC 模式,所以只展示实现类:
@Servicepublic class UserServiceImpl implements UserService { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private JwtUtil jwtUtil; @Value("${jwt.tokenHead}") private String tokenHead; @Override public RespBean login(String username, String password, HttpServletRequest request) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (userDetails==null || !passwordEncoder.matches(password,userDetails.getPassword())){ return RespBean.error("用户名或密码错误!"); } if (!userDetails.isEnabled()){ return RespBean.error("用户状态异常!"); } UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails,null, userDetails.getAuthorities()); String jwt = jwtUtil.foundJWT(userDetails); SecurityContextHolder.getContext().setAuthentication(token); Map<String,String> msg = new HashMap<>(); msg.put("tokenHead",tokenHead); msg.put("token", jwt); return RespBean.success("登录成功!",msg); }}复制代码7.1 完善 Swagger2Config 配置
由于 JWT 的加入,所以 Swagger2 的方法请求也是需要带入 JWT 令牌,提供了 Security 的全局认证。
只展示了修改的部分。
@Beanpublic Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) // 配置 apiInfo .select() // 选择那些路径和api会生成document .apis(RequestHandlerSelectors.basePackage("com.cnda.controller")) // // 对哪些 api进行监控,RequestHandlerSelectors.basePackage 基于包扫描 .paths(PathSelectors.any()) // 对所有路径进行监控 .build() // 添加和 Security 相关的配置。 .securityContexts(securityContexts()) .securitySchemes(securitySchemes());}// 以下方法相对于给 Swagger 添加了一个在 Security 的全局授权,并且以正则的形式设置了授权的请求 url/** * securityContexts * 请求体内容 */private List<SecurityContext> securityContexts(){ List<SecurityContext> securityContexts = new ArrayList<>(); securityContexts.add(getContextByPath("/hello/.*")); return securityContexts;}// 通过正则表达式来设置哪些路径// 通过 Path 获取到对应的 SecurityContextprivate SecurityContext getContextByPath(String pathRegex) { return SecurityContext.builder() .securityReferences(defaultAuth()) .forPaths(PathSelectors.regex(pathRegex)) // 按照 String 的 matches 方法进行匹配 .build();}/** * 配置默认的全局鉴权策略;其中返回的 SecurityReference 中,reference 即为 ApiKey 对象里面的name,保持一致才能开启全局鉴权 * @return SecurityReference */private List<SecurityReference> defaultAuth() { List<SecurityReference> references = new ArrayList<>(); // scope 参数: AuthorizationScope authorizationScope = new AuthorizationScope("global","accessEverything"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; references.add(new SecurityReference("Authorization",authorizationScopes)); return references;}/** * securitySchemes * 安全体方案 */private List<SecurityScheme> securitySchemes(){ List<SecurityScheme> apiKeys = new ArrayList<>(); // 设置请求头信息 apiKeys.add(new ApiKey("Authorization","Authorization","Header")); return apiKeys;}复制代码
修改的部分直接 CV 大法即可。
7.2 运行项目查看效果:
可以看到利用 Swagger2 的调试,返回 JWT Token 令牌成功!
{ "code": 200, "message": "登录成功!", "obj": { "tokenHead": "Bearer", "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlidCI6MTY3Nzk4NzIwNjgyMSwiZXhwIjoxNjc4NTkyMDA2fQ.p_GUqevx8gvCK2txxeEX-RQFm69yDCxCYNlZbeHgVIizSUDO6gaT3a2jGXvzXqofH2uxkQBgN4WfeSIlGydiNA" }}复制代码
将令牌设置到 Swagger2 中
这样之前的 /hello1 就可以请求成功了:
说明 Swagger2 设置 JWT 也成功了,每次发送请求,头部都会携带 JWT 令牌。
总结
还是对 Security 不太熟悉,Swagger2 的配置比较固定
JWT 主要也是两个点:
JWT Token Utile 工具类,主要用于管理 JWT 令牌。JWT Token Filter JWT 拦截器,这个就是 Security 和 JWT 的集成了,以及请求发来的时候解析 JWT 从而完成免登录这一操作。
原文链接:
标签: #登陆验证c语言