一、相关技术

1. Maven 项目管理工具

2. MybatisPlus

3. SpringBoot 2.7.0

4. Security 安全框架

5. Jwt

6. easy-captcha 验证码

7. swagger2 3.3.0

swagger2的3.3.0版本相关配置可以看我的相关博客(SpringBoot整合Swagger2)

二、技术简介

1. Security

1.1 简介

Spring Security是Spring家族中的一个重量级安全管理框架,实际上,在Spring Boot出现之前,Spring Security就已经发展了很多年了。Spring Boot为Spring Security提供了自动化配置方案。可以零配置使用Spring Security。

1.2 认证方式

  • form表单认证【推荐】
  • httpBasic认证

2. Jwt

2.1 简介

Json Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准,特别适用于分布式站点的单点登录(SSO)场景。

JWT广义:JWT就是签发token和校验token的一种机制。

JWT狭义:JWT就是token

基于token的鉴权机制类似于http协议也是无状态的,他不需要在服务端去保留用户的认证信息或者会话信息,这就意味着基于token认证机制的应用不需要考虑用户在哪一台服务器登陆了 这就为应用的扩展提供了便利。

2.2 官网图片描述

官网地址:JSON Web Token Introduction - jwt.io

JSON Web Token Introduction - jwt.io

2.3 Jwt组成

Jwt由三部分组成,头部、有效载荷、签名。

2.3.1 头部

头部用于描述该Jwt的最基本的信息。可以被表示成一个JSON对象。

2.3.2 载荷(Playload)

载荷就是存放有效信息的地方。有效信息包含以下部分:

(1)七个默认字段供选择(供选用)

  • iss (issuer):签发人

  • exp (expiration time):过期时间

  • sub (subject):主题

  • aud (audience):受众

  • nbf (Not Before):生效时间

  • iat (Issued At):签发时间

  • jti (JWT ID):编号

(2)私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64

是对称解密的,意味着该部分信息可以归类为明文信息。

2.3.3 签名

  • Signature 部分是对前两部分的签名,防止数据篡改。

  • 需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。

三、代码展示

1. pom.xml

        <!-- spring-boot-starter-security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--easy-captcha 验证码--><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version></dependency><!-- java-jwt 可以反编码过期token--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.18.3</version></dependency>

2. SecurityConfig.java(Security安全配置类)

/*** @author w* @createDate 2022/6/13* @description: 安全配置类*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 自定义用户认证逻辑*/@Resourceprivate MyUserDetailService userDetailService;@Resourceprivate MyAuthenticationSuccessHandler successHandler;@Resourceprivate MyAuthenticationFailureHandler failureHandler;/*** 认证不通过的处理类*/@Resourceprivate MyAuthenticationEntryPointHandler entryPointHandler;/*** 令牌认证过滤*/@Resourceprivate JwtAuthenticationFilter jwtAuthenticationFilter;/*** 匹配器*/@Resourceprivate JwtAuthenticationProvider provider;@Resourceprivate MyLogoutSuccessHandler logoutSuccessHandler;@Resourceprivate CorsFilter corsFilter;/*** 解决 无法直接注入 AuthenticationManager** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return super.authenticationManagerBean();}@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// jdbcauth.userDetailsService(userDetailService);// 自定义匹配器auth.authenticationProvider(provider);}/*** 自定义登录页面的页面放行* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http// CSRF禁用,因为不使用session.csrf().disable()// 没有token认证不通过.exceptionHandling().authenticationEntryPoint(entryPointHandler).and()// 基于token不使用session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 允许匿名访问,防止重定向锁死.antMatchers("/captchaImage","/login").anonymous().antMatchers("/swagger-ui/**").anonymous().antMatchers("/swagger/**").anonymous().antMatchers("/swagger-resources/**").anonymous().antMatchers("/v2/**").anonymous()// 除了以上,均要鉴权.anyRequest().authenticated().and() // 登出成功处理.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);http.addFilterBefore(corsFilter,JwtAuthenticationFilter.class);}
}

3. JwtUtil.java(令牌工具类)

/*** 令牌工具类*/
public class JwtUtil {// 服务器的密钥private static String secret = "123456";/*** 生成令牌** @param claims* @return*/public static String genToken(Map<String, String> claims , int expire) {// 有效时间Calendar instance = Calendar.getInstance();instance.add(Calendar.MINUTE, expire);// 载荷JWTCreator.Builder builder = JWT.create();claims.forEach((k,v)->builder.withClaim(k,v));// 设置有效期和签名=>生成令牌String token = builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(secret));return token;}/*** 校验令牌** @param token*/public static void verifyToken(String token) {JWT.require(Algorithm.HMAC256(secret)).build().verify(token);}/*** 解析令牌** @param token* @return*/public static String parseToken(String token,String key) {DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);return decodedJWT.getClaim(key).asString();}/*** 反编码过期令牌* @param token* @param key* @return*/public static String decodeExpireToken(String token, String key) {return JWT.decode(token).getClaim(key).asString();}
}

3. JwtAuthenticationFilter.java(令牌过滤器)

/*** @author w* @createDate 2022/6/13* @description: 令牌过滤器*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String requestURI = request.getRequestURI();if(requestURI.equals("/captchaImage") || requestURI.equals("/login")){filterChain.doFilter(request,response);return;}String accessToken = getTokenByRequest(request);if(StrUtil.isEmpty(accessToken)){response.setContentType("text/html;charset=utf-8");PrintWriter out = response.getWriter();out.write("没有令牌,请先登录");out.close();return;}// 把令牌放置到PrincipalJwtToken jwtToken = new JwtToken(accessToken);// 存储上下文认证信息try {SecurityContextHolder.getContext().setAuthentication(jwtToken);} catch (Exception e) {if(e.getCause()!=null && e.getCause() instanceof TokenExpiredException){responseFailResult(response,AjaxResult.fail(1001,"访问令牌过期"));return;}// 用户已经退出/用户在其他地方登录responseFailResult(response,AjaxResult.fail(1002,e.getMessage()));return;}filterChain.doFilter(request,response);}@SneakyThrowsprivate void responseFailResult(HttpServletResponse response, AjaxResult result) {response.setContentType("application/json;charset=utf-8");PrintWriter out = response.getWriter();out.println(JSONUtil.toJsonStr(result));}/*** 从请求头里获取访问令牌* @param request* @return*/private String getTokenByRequest(HttpServletRequest request) {String tokenStr = request.getHeader("Authorization");if(StrUtil.isNotEmpty(tokenStr) && tokenStr.startsWith("Bearer ")){// 返回令牌return tokenStr.replace("Bearer ","");}return "";}
}

4. MyAuthenticationEntryPointHandler.java(认证失败处理)

/*** @author w* @createDate 2022/6/13* @description: 认证失败的处理类,返回未授权*/
@Component
public class MyAuthenticationEntryPointHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");Map<String,Object> map = new HashMap<>();map.put("status",402);map.put("msg",e.getMessage());PrintWriter out = response.getWriter();out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}
}

5. MyLogoutSuccessHandler.java(登出处理)

/*** 登出处理*/
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");Map<String,Object> map = new HashMap<>();map.put("status",200);map.put("msg","登出成功");PrintWriter out = response.getWriter();out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}
}

6. JwtAuthenticationProvider.java(自定义匹配器)

/*** @author w* @createDate 2022/6/14* @description: 自定义匹配器*/
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {@Overridepublic boolean supports(Class<?> authentication) {return authentication.equals(JwtToken.class);}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String accessToken = (String) authentication.getPrincipal();//try {JwtUtil.verifyToken(accessToken);//} catch (Exception e) {//    throw new AuthenticationCredentialsNotFoundException("令牌校验失败");//}String userNo = JwtUtil.parseToken(accessToken, Constants.USERID);// 获取redis的访问令牌RedisTemplate redisTemplate = RedisBean.redis;String refreshToken = (String) redisTemplate.opsForValue().get(Constants.REFRESH_TOKEN_PREFIX + userNo);if(ObjectUtil.isEmpty(refreshToken)){throw new AuthenticationCredentialsNotFoundException("用户已退出");}// 判断时间戳String accessTokenCurrentTime = JwtUtil.parseToken(accessToken, Constants.CURRENTTIMEMILLIS);String refreshTokenCurrentTime = JwtUtil.parseToken(refreshToken, Constants.CURRENTTIMEMILLIS);if(!accessTokenCurrentTime.equalsIgnoreCase(refreshTokenCurrentTime)){throw new AuthenticationCredentialsNotFoundException("用户已在别处登录");}return new JwtToken(accessToken);}}

7. JwtToken.java(认证令牌)

/*** @author w* @createDate 2022/6/14* @description: 认证令牌*/
public class JwtToken extends AbstractAuthenticationToken {private String token;public JwtToken(String token) {super((Collection)null);this.setAuthenticated(false);this.token = token;}public JwtToken(Collection<? extends GrantedAuthority> authorities, String token) {super(authorities);this.setAuthenticated(true);this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return null;}
}

8. 登录的controller service serviceImpl

8.1 LoginController.java(登录控制层)

@RestController
public class LoginController {@Resourceprivate LoginServiceI loginService;@PostMapping("/login")public AjaxResult login(LoginVo loginVo){AjaxResult ajaxResult = AjaxResult.success();ajaxResult.put("token",loginService.login(loginVo));return ajaxResult;}
}

8.2 LoginServiceI.java(登录业务逻辑接口)

public interface LoginServiceI {String login(LoginVo loginVo);
}

8.3 LoginServiceImpl.java(登录业务逻辑)

@Service
public class LoginServiceImpl implements LoginServiceI {@Resourceprivate SysUserMapper sysUserMapper;@Resourceprivate RedisTemplate<String,String> redisTemplate;/*** 登录成功返回访问令牌给前端* @param loginVo* @return*/@Overridepublic String login(LoginVo loginVo) {validateCaptcha(loginVo.getCode(), loginVo.getUuid());// 验证用户名和密码SysUser sysUserDB = sysUserMapper.selectByUserName(loginVo.getUsername());if(ObjectUtil.isEmpty(sysUserDB)){// 用户名不存在throw new CustomException(CommonCode.USERNAME_ISNOT_EXIST);}if("1".equals(sysUserDB.getStatus())){throw new CustomException(CommonCode.USER_OUTAGE);}if("2".equals(sysUserDB.getDelFlag())){throw new CustomException(CommonCode.USER_IS_DELETE);}if(!PwdUtil.encode(loginVo.getPassword(),loginVo.getUsername()).equalsIgnoreCase(sysUserDB.getPassword())){throw new CustomException(CommonCode.USERNAME_OR_PASSWORD_ERROR);}// 生成令牌// 获得系统毫秒数long currentTimeMillis = System.currentTimeMillis();// 载荷信息,JWT令牌中不存放敏感信息Map<String, String> claims = new HashMap<>();claims.put(Constants.USERID,String.valueOf(sysUserDB.getUserId()));claims.put(Constants.USERNAME,sysUserDB.getUserName());claims.put(Constants.CURRENTTIMEMILLIS,String.valueOf(currentTimeMillis));// 访问令牌返回给前端String accessToken = JwtUtil.genToken(claims,Constants.EXPIRE_ACCESS_TOKEN_TIME);// 刷新令牌写入redisString refreshToken = JwtUtil.genToken(claims, Constants.EXPIRE_REFRESH_TOKEN_TIME);redisTemplate.opsForValue().set(Constants.REFRESH_TOKEN_PREFIX+sysUserDB.getUserId(),refreshToken,Constants.EXPIRE_REFRESH_TOKEN_TIME, TimeUnit.MINUTES);return accessToken;}/*** 验证码认证* @param code* @param uuid*/private void validateCaptcha(String code, String uuid) {String key = Constants.CAPTCHA_CODE_PREFIX + uuid;String realCode = redisTemplate.opsForValue().get(key);if(StrUtil.isNotEmpty(realCode)){if(code.equalsIgnoreCase(realCode)){redisTemplate.delete(key);return;}else {throw new CustomException(CommonCode.CAPTCHA_ERROR);}}else {throw new CustomException(CommonCode.CAPTCHA_EXPIRE);}}

9. 授权

在controller层使用注解@PreAuthorize("hasAuthority('xxx:xxx:xxx')")进行授权。

例如:

UserController.java

使用一个方法举例。

@RestController
@RequestMapping("/system/user")
@Api(tags = "用户信息表接口")
public class SysUserController {/*** 用户信息表业务层*/@Resourceprivate SysUserServiceI sysUserService;/*** 增加用户信息表* @param sysUser* @return*/@PostMapping@ApiOperation("增加用户信息表")@PreAuthorize("hasAuthority('system:user:add')")public AjaxResult add(@RequestBody SysUser sysUser){sysUserService.add(sysUser);return AjaxResult.success();}
}

SpringBoot+Security+Jwt登录认证与权限控制(一)相关推荐

  1. Springboot + Spring Security 实现前后端分离登录认证及权限控制

    Spring Security简介 Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展 ...

  2. spring security+jwt 登录认证

    spring security+jwt 登录认证 1.综述 2.版本与环境 3.架构 4.数据库认证逻辑图 5.案例 security+jwt 5.1引入依赖 5.2新建工具类 5.2新建组件类 5. ...

  3. springboot+mybatis整合shiro——登录认证和权限控制

    引依赖 shiro-all包含shiro所有的包.shiro-core是核心包.shiro-web是与web整合.shiro-spring是与spring整合.shiro-ehcache是与EHCac ...

  4. Spring Security用户认证和权限控制(默认实现)

    1 背景 实际应用系统中,为了安全起见,一般都必备用户认证(登录)和权限控制的功能,以识别用户是否合法,以及根据权限来控制用户是否能够执行某项操作. Spring Security是一个安全相关的框架 ...

  5. shiro实现APP、web统一登录认证和权限管理

    先说下背景,项目包含一个管理系统(web)和门户网站(web),还有一个手机APP(包括Android和IOS),三个系统共用一个后端,在后端使用shiro进行登录认证和权限控制.好的,那么问题来了w ...

  6. SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

    文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...

  7. 自定义request_Spring Security 自定义登录认证(二)

    一.前言 本篇文章将讲述Spring Security自定义登录认证校验用户名.密码,自定义密码加密方式,以及在前后端分离的情况下认证失败或成功处理返回json格式数据 温馨小提示:Spring Se ...

  8. SpringBoot Security 自定义登录验证逻辑+密码加盐

    密码加盐思路 JAVA 加盐加密方法_Teln_小凯的博客-CSDN博客 盐加密方法 @ApiOperation(value = "002-加密")@PreAuthorize(&q ...

  9. 用Python建设企业认证和权限控制平台

    目前大家对Python的了解更多来源是数据分析.AI.运维工具开发,在行业中使用Python进行web开发,同样也是非常受欢迎的,例如:FaceBook,豆瓣,知乎,饿了么等等,本文主要是介绍是利用P ...

最新文章

  1. C 整数反转
  2. 【实践案例】Databricks 数据洞察 Delta Lake 在基智科技(STEPONE)的应用实践
  3. Android Studio(3)---Android Studio的配置
  4. it : Tmaster (hook declined) error: failed to push some refs to https://xxx/biluo/xxx.git
  5. Bootstrap的下拉列表点击没有用
  6. UVA11752 The Super Powers【超级幂+暴力+数论】
  7. js得到自定义属性和操作table表格
  8. linux上apache的安装
  9. 网摘Android调用WebService
  10. 转载——yum源的超级简单配置
  11. Mac OS用Anaconda安装Jupyter Notebook
  12. 免费分享一套狂雨小说cms采集规则
  13. C语言编程实例(一)
  14. 使用邮件合并批量制作工资条并进行发送邮件
  15. 计算机控制系统的数字量输出通道由,计算机控制-习题
  16. IDEA社区版下载与安装详细教程
  17. 企业邮箱怎样申请注册?
  18. DLKcat开发细则(自用)
  19. 像素(px)与厘米的关系
  20. linux 文件备份工具,四种时下流行 Linux备份工具比较与操作实例

热门文章

  1. 如何快速删除多张ppt上的多张一样的图
  2. 论坛文中前面加html,社区 论坛 美化帖子HTML基础代码 文字移动特效 及图片加字代码...
  3. RegisterHotKey
  4. 【Qt】对话框QDialog类,模态对话框和非模态对话框
  5. 有什么好用的抠章方法?这个方法快来了解了解
  6. L2-027 名人堂与代金券 (25分)
  7. 在.htaccess文件中写RewriteRule无效的问题的解决
  8. java的synthetic_探索Java中隐藏的访问权限synthetic
  9. vue数据修改之后没有同步渲染出来,需要点一下屏幕才会进行页面更新(原因和解决方法)
  10. 大师兄教你如何过华为机试