Oauth2.0实现token刷新功能(二)

个人技术分享交流群:1125844267,大家可以进来唠嗑闲聊


前言

之前写过一篇“Oauth2.0实现token刷新功能”,发现大家阅读的还是特别多,那个是基于Spring Cloud实现的刷新功能,现在将它改为基于普通过滤器实现token刷新。


直接上代码

基本思路还是跟之前一样,过滤器拦截请求验证token,token过期后请求单点登录服务器换取新的access_token和refresh_token,将两个token放到过滤器返回体的头部。从而不影响本次请求,同时还能在本次请求的过程中做到无痕刷新token。

@Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        //请求的全路径
        String url = request.getRequestURI();
        log.info("拦截到的请求路径:" + url);
        AtomicBoolean matchFlag = new AtomicBoolean(true);
        //配置文件配置的不需要过滤器拦截的请求路径
        List<String> urlList = interceptorPathBean.getInclude();
        for (String excludeUrl : urlList) {
            if (url.startsWith(excludeUrl)) {
                matchFlag.set(false);
                break;
            }
        }
        //不过滤oprions请求
        if(((HttpServletRequest) req).getMethod().equals(RequestMethod.OPTIONS.name())){
            matchFlag.set(false);
        }
        if(matchFlag.get()){   //需要过滤
            //过滤前端的请求,校验token(前端每次请求需要携带token)
            String accessToken = request.getHeader("Authorization");
            //前端请求的refreshToken
            String refreshToken = request.getHeader("refreshToken");
            MyUser myUser;
            try {
            	//这儿就直接解析前端传过来的token,没有去单点登录服务器验证
            	//如果解析成功就证明token有效
            	//验证用的包应该是com.auth0.java-jwt,自己查一下
                myUser = JwtUtil.parseJWT(accessToken);   
            } catch (TokenExpiredException e) {
                log.info("accessToken过期,尝试刷新token");
                //这个http请求的方法下边给一下,必须是basic验证的方式请求
                String result = HttpClientForOauth.sendHttpPost(oauthValue.getRefreshTokeUriPrefix() + refreshToken,
                        oauthValue.getClientId(),
                        oauthValue.getClientSecret());

                Map<String, Object> resultMap = JSONArray.parseObject(result);
                //获取到token
                String accessToken2 = String.valueOf(resultMap.get("access_token"));
                //获取到refresh_token信息
                String refreshToken2 = String.valueOf(resultMap.get("refresh_token"));
                //获取到error信息(接口报错,通常是因为refresh_token也过期了)
                String error = String.valueOf(resultMap.get("error"));
                //说明刷新成功,更新token
                if (accessToken2 != null && refreshToken2 != null && "null".equals(error)) {
                    myUser = JwtUtil.parseJWT(accessToken2);
                    request.setAttribute("user", myUser);
                    response.setHeader("Access-Control-Expose-Headers", "accessToken,refreshToken");       //必须加,否则vue无法获取头中的信息
                    //response.setHeader("Access-Control-Expose-Headers", "refreshToken");//必须加,否则vue无法获取头中的信息
                    response.setHeader("accessToken", accessToken2);
                    response.setHeader("refreshToken", refreshToken2);
                } else {
                    log.error("Token已过期,refreshToken也过期");
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    return;
                }
            } catch (UnsupportedJwtException e) {
                log.error("Token格式错误");
                //response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
                return;
            } catch (MalformedJwtException e) {
                log.error("Token没有被正确构造");
                response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
                return;
            } catch (SignatureException e) {
                log.error("签名失败");
                response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
                return;
            } catch (IllegalArgumentException e) {
                log.error("非法参数异常{}",e);
                response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
                return;
            } catch (Exception e) {
                log.error("其它所有异常",e);
                response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
                return;
            }
            //将token解析出来的用户信息放入请求头,以便服务确定当前登录用户
            request.setAttribute("user", myUser);
            chain.doFilter(request, response);
        }else{                 //不需要过滤
            chain.doFilter(request, response);
        }
    }

上边换取新的token用到的http请求,其实也是用code获取token的请求

public static String sendHttpPost(String httpUrl, String clientId, String clientSecret) {
		HttpPost httpPost = new HttpPost(httpUrl);
		CloseableHttpClient httpClient = null;
		CloseableHttpResponse response = null;
		HttpEntity entity;
		String responseContent = null;
		try {
			// 创建默认的httpClient实例.
			httpClient = HttpClients.createDefault();
			//单点登录验证code时需要的客户端用户名密码
			String encoding = DatatypeConverter.printBase64Binary((clientId+":"+clientSecret).getBytes("UTF-8"));
			httpPost.setHeader("Authorization", "Basic " +encoding);
			// 执行请求
			response = httpClient.execute(httpPost);
			entity = response.getEntity();
			responseContent = EntityUtils.toString(entity, "UTF-8");
		} catch (Exception e) {
			log.error("身份验证接口错误",e);
		} finally {
			try {
				// 关闭连接,释放资源
				if (response != null) {
					response.close();
				}
				if (httpClient != null) {
					httpClient.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return responseContent;
	}

再贴一个解决跨域的过滤器,可能会用到。如果需要,这个过滤器应该在刷新token的过滤器之前执行(设置@Order的值)

@Component
@Order(value=-1)
public class CrossFilter implements Filter {

    /*跨域请求配置*/
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest reqs = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin",reqs.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "5000");
        response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,Authorization,X-Token,accessToken,refreshToken");
        //response.setHeader("Access-Control-Expose-Headers","accessToken,refreshToken");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        chain.doFilter(reqs, res);
    }
    @Override
    public void init(FilterConfig filterConfig) {}
    @Override
    public void destroy() {}
}

总结

这种方式前端需要在拦截请求的时候每次查看返回体的头部有没有新的token,如果有就将前端保存的token替换成新的,从而实现token的刷新

嗨!关于Spring Boot整合Spring Security和OAuth2.0实现token认证,你可以按照以下步骤进行操作: 1. 添加依赖:在你的Spring Boot项目的pom.xml文件中,添加Spring Security和OAuth2.0相关的依赖。 ```xml <dependencies> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Spring Security OAuth2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> </dependencies> ``` 2. 配置Spring Security:创建一个继承自WebSecurityConfigurerAdapter的配置类,并重写configure方法来配置Spring Security的行为。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/oauth2/**", "/login/**", "/logout/**") .permitAll() .anyRequest() .authenticated() .and() .oauth2Login() .loginPage("/login") .and() .logout() .logoutSuccessUrl("/") .invalidateHttpSession(true) .clearAuthentication(true) .deleteCookies("JSESSIONID"); } } ``` 在上述配置中,我们允许访问一些特定的URL(如/oauth2/**,/login/**和/logout/**),并保护所有其他URL。我们还设置了自定义的登录页面和注销成功后的跳转页面。 3. 配置OAuth2.0:创建一个继承自AuthorizationServerConfigurerAdapter的配置类,并重写configure方法来配置OAuth2.0的行为。 ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory() .withClient("client_id") .secret("client_secret") .authorizedGrantTypes("authorization_code", "password", "refresh_token") .scopes("read", "write") .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(86400); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager); } } ``` 在上述配置中,我们使用内存存储客户端信息(client_id和client_secret),并配置了授权类型(如authorization_code、password和refresh_token)。我们还设置了访问令牌和刷新令牌的有效期。 4. 创建登录页面:创建一个HTML登录页面,用于用户进行身份验证并获取访问令牌。 ```html <!DOCTYPE html> <html> <head> <title>Login</title> </head> <body> <h2>Login</h2> <form th:action="@{/login}" method="post"> <div> <label for="username">Username:</label> <input type="text" id="username" name="username" /> </div> <div> <label for="password">Password:</label> <input type="password" id="password" name="password" /> </div> <div> <button type="submit">Login</button> </div> </form> </body> </html> ``` 5. 处理登录请求:创建一个控制器来处理登录请求,并在登录成功后重定向到受保护的资源。 ```java @Controller public class LoginController { @GetMapping("/login") public String showLoginForm() { return "login"; } @PostMapping("/login") public String loginSuccess() { return "redirect:/protected-resource"; } } ``` 在上述控制器中,我们使用@GetMapping注解来处理GET请求,@PostMapping注解来处理POST请求。登录成功后,我们将用户重定向到受保护的资源。 这样,你就完成了Spring Boot整合Spring Security和OAuth2.0实现token认证的配置。你可以根据自己的需求进行进一步的定制和扩展。希望对你有所帮助!如果你有任何疑问,请随时问我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Happy-Sir

有收获请打赏,哈哈

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值