• SpringSecurity+JWT


    Spring Security

    一堆过滤器链的集合。
    在这里插入图片描述

    使用

    引入依赖

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-securityartifactId>
    dependency>
    
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwtartifactId>
        <version>0.9.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    SpringBoot启动类上添加注解:@EnableWebSecurity

    springboot配置文件中配置security默认用户名密码

     security:
        user:
          name: admin
          password: admin
    
    • 1
    • 2
    • 3
    • 4

    用户认证

    分为两种:

    • 首次登录验证
    • 二次token认证

    验证码过滤器:

    因为前后端分离禁用session,所以把验证码存入到redis中。
    在这里插入图片描述

    验证码controller(可选)

    @Slf4j
    @RestController
    public class AuthController extends BaseController{
       @Autowired
       private Producer producer;
       /**
        * 图片验证码
        */
       @GetMapping("/captcha")
       public Result captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
          String code = producer.createText();
          String key = UUID.randomUUID().toString();
    
          BufferedImage image = producer.createImage(code);
          ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
          ImageIO.write(image, "jpg", outputStream);
          BASE64Encoder encoder = new BASE64Encoder();
          String str = "data:image/jpeg;base64,";
          String base64Img = str + encoder.encode(outputStream.toByteArray());
          
          // 存储到redis中
          redisUtil.hset(Const.captcha_KEY, key, code, 120);
          log.info("验证码 -- {} - {}", key, code);
          return Result.succ(
                MapUtil.builder()
                .put("token", key)
                .put("base64Img", base64Img)
                .build()
          );
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    解决跨域(可选)

    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
    
    	private CorsConfiguration buildConfig() {
    		CorsConfiguration corsConfiguration = new CorsConfiguration();
    		corsConfiguration.addAllowedOrigin("*");
    		corsConfiguration.addAllowedHeader("*");
    		corsConfiguration.addAllowedMethod("*");
    		corsConfiguration.addExposedHeader("Authorization");
    		return corsConfiguration;
    	}
    
    	@Bean
    	public CorsFilter corsFilter() {
    		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    		source.registerCorsConfiguration("/**", buildConfig());
    		return new CorsFilter(source);
    	}
    
    	@Override
    	public void addCorsMappings(CorsRegistry registry) {
    		registry.addMapping("/**")
    				.allowedOrigins("*")
    //          .allowCredentials(true)
    				.allowedMethods("GET", "POST", "DELETE", "PUT")
    				.maxAge(3600);
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    配置security总配置文件

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    	@Autowired
    	LoginFailureHandler loginFailureHandler;
    
    	@Autowired
    	LoginSuccessHandler loginSuccessHandler;
    
    	@Autowired
    	CaptchaFilter captchaFilter;
    
    	@Autowired
    	JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    
    	@Autowired
    	JwtAccessDeniedHandler jwtAccessDeniedHandler;
    
    	@Autowired
    	UserDetailServiceImpl userDetailService;
    
    	@Autowired
    	JwtLogoutSuccessHandler jwtLogoutSuccessHandler;
    
    	@Bean
    	JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
    		JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
    		return jwtAuthenticationFilter;
    	}
    
        //告诉security使用了哪种密码加密方式
    	@Bean
    	BCryptPasswordEncoder bCryptPasswordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
    
        //拦截白名单
    	private static final String[] URL_WHITELIST = {
    
    			"/login",
    			"/logout",
    			"/captcha",
    			"/favicon.ico",
    	};
    
    
    	protected void configure(HttpSecurity http) throws Exception {
    
    		http.cors().and().csrf().disable()
    
    				// 登录配置
    				.formLogin()
    				.successHandler(loginSuccessHandler)
    				.failureHandler(loginFailureHandler)
    
    				.and()
    				.logout()
    				.logoutSuccessHandler(jwtLogoutSuccessHandler)
    
    				// 禁用session
    				.and()
    				.sessionManagement()
    				.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    
    				// 配置拦截规则
    				.and()
    				.authorizeRequests()
    				.antMatchers(URL_WHITELIST).permitAll()
    				.anyRequest().authenticated()
    
    				// 异常处理器
    				.and()
    				.exceptionHandling()
    				.authenticationEntryPoint(jwtAuthenticationEntryPoint)
    				.accessDeniedHandler(jwtAccessDeniedHandler)
    
    				// 配置自定义的过滤器
    				.and()
    				.addFilter(jwtAuthenticationFilter())
    				.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)  //先校验验证码
    
    		;
    
    	}
    
    	@Override
    	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		auth.userDetailsService(userDetailService);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    创建security包,配置过滤器链

    • 登录成功处理器
    @Component
    public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    	@Autowired
    	JwtUtils jwtUtils;
    
    	@Override
    	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    		response.setContentType("application/json;charset=UTF-8");
    		ServletOutputStream outputStream = response.getOutputStream();
    
    		// 生成jwt,并放置到请求头中
    		String jwt = jwtUtils.generateToken(authentication.getName());
    		response.setHeader(jwtUtils.getHeader(), jwt);
    
    		Result result = Result.succ("");
    
    		outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
    
    		outputStream.flush();
    		outputStream.close();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 登陆失败处理器
    @Component
    public class LoginFailureHandler implements AuthenticationFailureHandler {
    	@Override
    	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    
    		response.setContentType("application/json;charset=UTF-8");
    		ServletOutputStream outputStream = response.getOutputStream();
    
    		Result result = Result.fail("用户名或密码错误");
    
    		outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
    
    		outputStream.flush();
    		outputStream.close();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • JWT验证过滤器

      每次请求都会验证是否登录,token是否正确

    public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
    	@Autowired
    	JwtUtils jwtUtils;
    
    	@Autowired
    	UserDetailServiceImpl userDetailService;
    
    	@Autowired
    	SysUserService sysUserService;
    
    	public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
    		super(authenticationManager);
    	}
    
    	@Override
    	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            //为空也放行,可能是静态资源
    		String jwt = request.getHeader(jwtUtils.getHeader());
    		if (StrUtil.isBlankOrUndefined(jwt)) {
    			chain.doFilter(request, response);
    			return;
    		}
    
    		Claims claim = jwtUtils.getClaimByToken(jwt);
    		if (claim == null) {
    			throw new JwtException("token 异常");
    		}
    		if (jwtUtils.isTokenExpired(claim)) {
    			throw new JwtException("token已过期");
    		}
    
    		String username = claim.getSubject();
    		// 获取用户的权限等信息
    
    		SysUser sysUser = sysUserService.getByUsername(username);
    		UsernamePasswordAuthenticationToken token
    				= new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(sysUser.getId()));
    
    		SecurityContextHolder.getContext().setAuthentication(token);
    
    		chain.doFilter(request, response);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • JWT错误过滤器

      用来统一JWT验证错误的返回结果,而不是返回一个默认登录页面,因为前后端分离项目不需要返回页面。

    @Component
    public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    	@Override
    	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    
    		response.setContentType("application/json;charset=UTF-8");
    		response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    		ServletOutputStream outputStream = response.getOutputStream();
    
    		Result result = Result.fail("请先登录");
    
    		outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
    
    		outputStream.flush();
    		outputStream.close();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 用户权限不足过滤器

      用户若没有相应的访问权限则统一返回失败结果。

    @Component
    public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    	@Override
    	public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    
    		response.setContentType("application/json;charset=UTF-8");
    		response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    
    		ServletOutputStream outputStream = response.getOutputStream();
    
    		Result result = Result.fail(accessDeniedException.getMessage());
    
    		outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
    
    		outputStream.flush();
    		outputStream.close();
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 验证码过滤器

      在校验登录账号密码前先校验验证码。

      @Component
      public class CaptchaFilter extends OncePerRequestFilter {
      	@Autowired
      	RedisUtil redisUtil;
      
      	@Autowired
      	LoginFailureHandler loginFailureHandler;
      
      	@Override
      	protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
      
      		String url = httpServletRequest.getRequestURI();
      
      		if ("/login".equals(url) && httpServletRequest.getMethod().equals("POST")) {
      
      			try{
      				// 校验验证码
      				validate(httpServletRequest);
      			} catch (CaptchaException e) {
      
      				// 交给认证失败处理器
      				loginFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
      			}
      		}
      
      		filterChain.doFilter(httpServletRequest, httpServletResponse);
      	}
      
      	// 校验验证码逻辑
      	private void validate(HttpServletRequest httpServletRequest) {
      
      		String code = httpServletRequest.getParameter("code");
      		String key = httpServletRequest.getParameter("token");
      
      		if (StringUtils.isBlank(code) || StringUtils.isBlank(key)) {
      			throw new CaptchaException("验证码错误");
      		}
      
      		if (!code.equals(redisUtil.hget(Const.CAPTCHA_KEY, key))) {
      			throw new CaptchaException("验证码错误");
      		}
      
      		// 一次性使用
      		redisUtil.hdel(Const.CAPTCHA_KEY, key);
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46

    从数据库读取用户密码匹配

    也都可放入security包中。

    • 可以使用security提供的User类也可以模仿他的自己再封装一个:
    public class AccountUser implements UserDetails {
    	private Long userId;
    	private String password;
    	private final String username;
    	private final Collection<? extends GrantedAuthority> authorities;
    	private final boolean accountNonExpired;
    	private final boolean accountNonLocked;
    	private final boolean credentialsNonExpired;
    	private final boolean enabled;
    
    	public AccountUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
    		this(userId, username, password, true, true, true, true, authorities);
    	}
    
    
    	public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired,
    	            boolean credentialsNonExpired, boolean accountNonLocked,
    	            Collection<? extends GrantedAuthority> authorities) {
    		Assert.isTrue(username != null && !"".equals(username) && password != null,
    				"Cannot pass null or empty values to constructor");
    		this.userId = userId;
    		this.username = username;
    		this.password = password;
    		this.enabled = enabled;
    		this.accountNonExpired = accountNonExpired;
    		this.credentialsNonExpired = credentialsNonExpired;
    		this.accountNonLocked = accountNonLocked;
    		this.authorities = authorities;
    	}
    
    	@Override
    	public Collection<? extends GrantedAuthority> getAuthorities() {
    		return this.authorities;
    	}
    	@Override
    	public String getPassword() {
    		return this.password;
    	}
    	@Override
    	public String getUsername() {
    		return this.username;
    	}
    	@Override
    	public boolean isAccountNonExpired() {
    		return this.accountNonExpired;
    	}
    	@Override
    	public boolean isAccountNonLocked() {
    		return this.accountNonLocked;
    	}
    	@Override
    	public boolean isCredentialsNonExpired() {
    		return this.credentialsNonExpired;
    	}
    	@Override
    	public boolean isEnabled() {
    		return this.enabled;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 返回封装的User对象,让security进行用户名密码比对:
    @Service
    public class UserDetailServiceImpl implements UserDetailsService {
    
    	@Autowired
    	SysUserService sysUserService;
    
        //从数据库加载封装对应用户名的正确的用户实体对象
    	@Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    		SysUser sysUser = sysUserService.getByUsername(username);
    		if (sysUser == null) {
    			throw new UsernameNotFoundException("用户名或密码不正确");
    		}
    		return new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));
    	}
    
    	/**
    	 * 获取用户权限信息(角色、菜单权限)
    	 * @param userId
    	 * @return
    	 */
    	public List<GrantedAuthority> getUserAuthority(Long userId){
    		// 角色(ROLE_admin)、菜单操作权限 sys:user:list
    		String authority = sysUserService.getUserAuthorityInfo(userId);  // ROLE_admin,ROLE_normal,sys:user:list,....   
            //见下面代码
    
    		return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    其中getUserAuthorityInfo(userId)根据用户ID查出所有权限的字符串格式代码:

    @Service
    public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
    	@Autowired
    	SysRoleService sysRoleService;
    	@Autowired
    	SysUserMapper sysUserMapper;
    	@Autowired
    	SysMenuService sysMenuService;
    	@Autowired
    	RedisUtil redisUtil;
    
    	@Override
    	public SysUser getByUsername(String username) {
    		return getOne(new QueryWrapper<SysUser>().eq("username", username));
    	}
    
    	@Override
    	public String getUserAuthorityInfo(Long userId) {
    
    		SysUser sysUser = sysUserMapper.selectById(userId);
    
    		//  ROLE_admin,ROLE_normal,sys:user:list,....
    		String authority = "";
    
            //如果redis缓存了就从redis中取,其实这里存入userID要比username在删除缓存时更简单点
    		if (redisUtil.hasKey("GrantedAuthority:" + sysUser.getUsername())) {
    			authority = (String) redisUtil.get("GrantedAuthority:" + sysUser.getUsername());
    
    		} else {
    			// 获取角色编码
    			List<SysRole> roles = sysRoleService.list(new QueryWrapper<SysRole>()
    					.inSql("id", "select role_id from sys_user_role where user_id = " + userId));
    
    			if (roles.size() > 0) {
    				String roleCodes = roles.stream().map(r -> "ROLE_" + r.getCode()).collect(Collectors.joining(","));
    				authority = roleCodes.concat(",");
    			}
    
    			// 获取菜单操作编码
    			List<Long> menuIds = sysUserMapper.getNavMenuIds(userId);
    			if (menuIds.size() > 0) {
    
    				List<SysMenu> menus = sysMenuService.listByIds(menuIds);
    				String menuPerms = menus.stream().map(m -> m.getPerms()).collect(Collectors.joining(","));
    
    				authority = authority.concat(menuPerms);
    			}
    
    			redisUtil.set("GrantedAuthority:" + sysUser.getUsername(), authority, 60 * 60);
    		}
    
    		return authority;
    	}
    
        
        
        
        //发生变动时删除redis缓存
    	@Override
    	public void clearUserAuthorityInfo(String username) {//用户权限变了
    		redisUtil.del("GrantedAuthority:" + username);
    	}
    	@Override
    	public void clearUserAuthorityInfoByRoleId(Long roleId) {//角色名等变了
    		List<SysUser> sysUsers = this.list(new QueryWrapper<SysUser>()
    				.inSql("id", "select user_id from sys_user_role where role_id = " + roleId));
    		sysUsers.forEach(u -> {
    			this.clearUserAuthorityInfo(u.getUsername());
    		});
    	}
    	@Override
    	public void clearUserAuthorityInfoByMenuId(Long menuId) {//权限名等变了
    		List<SysUser> sysUsers = sysUserMapper.listByMenuId(menuId);
    		sysUsers.forEach(u -> {
    			this.clearUserAuthorityInfo(u.getUsername());
    		});
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    表结构:
    在这里插入图片描述

    权限

    需要设置两处权限:

    • 第一次登陆的UserDetailServiceImpl里

    • 登陆后调用接口的JWT验证过滤器里

    使用注解在接口上标注需要的权限:

    • 比如需要Admin角色权限:

      @PreAuthorize(“hasRole(‘admin’)”)

    • 比如需要添加用户的操作权限

      @PreAuthorize(“hasAuthority(‘sys:user:save’)”)

    退出登录

    退出登录处理器,放在security包中。并在security总配置类中指定该处理器即可。

    @Component
    public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {
    	@Autowired
    	JwtUtils jwtUtils;
    
    	@Override
    	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    
    		if (authentication != null) {
    			new SecurityContextLogoutHandler().logout(request, response, authentication);
    		}
    
    		response.setContentType("application/json;charset=UTF-8");
    		ServletOutputStream outputStream = response.getOutputStream();
    
    		response.setHeader(jwtUtils.getHeader(), "");
    
    		Result result = Result.succ("");
    
    		outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
    
    		outputStream.flush();
    		outputStream.close();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    注意:

    默认的登录请求url是"/login",并且只允许POST方式的请求。

    在Controller中获取当前登录用户信息可以在参数列表中接收Principal对象。

    UserDetailsService 返回给security封装的User对象进行用户名密码自动校验。

    PasswordEncoder 同样的密码每次加密的密文也不一样。

    JWT

    由Json对象组成的web令牌,替代传统登录保存到服务器内存的session的方式。

    由于JWT基于客户端所以重启服务器JWT也不会失效。

    1,授权

    主流用法,一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务,资源。单点登录就是JWT的一项功能,因为它开销很小且跨域轻松。

    2,信息交换

    JWT是在各方之间安全传输信息的好方法,因为可以对JWT进行签名(使用公钥/私钥对),所以你可以确保发件人是他们所说的,此外你还可以验证内容是否遭到篡改。

    JWT认证流程

    前端将用户名密码发送给后端,后端核对成功后根据用户信息生成一个JWT(Token),后端将JWT作为登陆成功返回结果给前端,前端保存在本地localStorage或sessionStorage上(退出登录时前端删除保存的JWT即可),前端之后每次请求时将JWT放入HTTP Header中,后端校验JWT有效性,如签名是否正确、Token是否过期、检查Token的接收方是否是自己等,后端验证成功后使用JWT中的用户信息完成相关请求操作。

    JWT结构

    标头.有效载荷.签名
    在这里插入图片描述

    通过Base64编码成字符串
    在这里插入图片描述

    注意:由于Base64编码是可逆的,请勿在JWT中存放敏感信息。

    使用

    1.引入依赖

    
    <dependency>
      <groupId>com.auth0groupId>
      <artifactId>java-jwtartifactId>
      <version>3.4.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2,生成token

    HashMap<String,Object> map =new HashMap<>();
    
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.SECOND, 90);
    //生成令牌
    String token = JWT.create()
      .WithHeader(map)   //header,可省略使用默认
      .withClaim("userId", 12)    //设置自定义用户名
      .withClaim("username", "张三")
      .withExpiresAt(instance.getTime())   //设置过期时间
      .sign(Algorithm.HMAC256("!Q2W#E$RW"));   //设置签名 保密 复杂
    //输出令牌
    System.out.println(token);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.验证token

    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!Q2W#E$RW")).build();
    //验证失败会抛异常
    DecodedJWT decodedJWT = jwtVerifier.verify(token);
    
    //存的是时候是什么类型,取得时候就是什么类型,否则取不到值。
    System.out.println("用户名: " + decodedJWT.getClaim("userId").asInt()); 
    System.out.println("用户名: " + decodedJWT.getClaim("username").asString()); 
    System.out.println("过期时间: "+decodedJWT.getExpiresAt());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4,使用JWT工具类

    public class JWTUtils {
    
        private static String SECRET_KEY = "1123vbnmASDLQWERTYUIOZXCVBNM";
    
        //根据用户信息生成token
        public static String createToken(CurrentUser currentUser) {
            Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MINUTE, 30);
            String token = JWT.create()
                    .withClaim("username", currentUser.getUsername())
                    .withExpiresAt(calendar.getTime())
                    .sign(algorithm);
            return token;
        }
    
        //校检 token 是否过期
        private static DecodedJWT verifyToken(String token) {
            return JWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(token);
        }
    
       	//根据 token 解析出用户名
        public static String parseUserInfo(String token) {
            DecodedJWT decodedJWT = verifyToken(token);
            return decodedJWT.getClaim("username").asString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    由于很多接口都需要验证JWT,单体应用可以使用拦截器,分布式微服务可以使用网关优化。

  • 相关阅读:
    【宋红康 MySQL数据库 】【高级篇】【11】索引的设计原则
    矢量叉乘能否反求矢量
    FastAPI 学习之路(十四)响应模型
    iOS经典面试题之深入解析分类Category的本质以及如何被加载
    部署k8s集群-containerd
    IntelliJ IDEA 创建 Java 工程,运行 HelloWorld
    你知道如何实现游戏中的透视效果吗?
    联系与合作
    短期风速预测|LSTM|ELM|批处理(matlab代码)
    【three.js】坐标辅助器和轨道控制器
  • 原文地址:https://blog.csdn.net/m0_48268301/article/details/126911689