springcloudgateway+oauth2实现权限控制

it2023-02-17  82

文章目录

鉴权服务基础配置鉴权配置 网关服务基础配置网关过滤器配置权限校验过滤器配置 接口调试前端适配

鉴权服务

OAuth 2.0 的四种方式OAuth2实现分析

基础配置

新建house-oauth模块,依赖oauth2。 <dependencies> <dependency> <groupId>com.babyjuan</groupId> <artifactId>house-common</artifactId> <version>${house.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> </dependencies> bootstrap.properties初始化依赖的nacos配置。 spring.application.name=house-oauth spring.profiles.active=oauth-application spring.cloud.nacos.config.namespace=${HOUSE_ENV} spring.cloud.nacos.config.group=house spring.cloud.nacos.config.server-addr=${NACOS_ADDR} spring.cloud.nacos.config.prefix=house spring.cloud.nacos.config.file-extension=yaml nacos的house-oauth-application.yaml中配置端口为6001。 server: port: 6001 spring: application: name: oauth2-server

鉴权配置

初始化基础配置:认证管理器、加密方式。 @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { //默认的认证操作 return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { //加密器 return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //允许匿名访问所有接口 主要是 oauth 接口 http.authorizeRequests().antMatchers("/**").permitAll(); } } 继承spring-security的UserDetailsService,管理用户密码、角色信息。 @Service public class UserServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //TODO 用户持久化 if ("admin".equals(username)) { String role = "ROLE_ADMIN"; List<SimpleGrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(role)); String password = passwordEncoder.encode("123456"); return new User(username, password, authorities); } throw new UsernameNotFoundException("no user"); } } 配置默认资源(即接口)管理服务。如果不使用默认实现,可参考。 @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { } 配置授权服务。 @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired public PasswordEncoder passwordEncoder; @Autowired public UserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Override public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //TODO token持久化 //配置授权服务策略 endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //TODO 客户端持久化 //配置网关服务的用户名密码,仅网关服务可作为客户端可访问oauth服务 clients.inMemory() .withClient("gateway-client").secret(passwordEncoder.encode("123456")) .authorizedGrantTypes("refresh_token", "authorization_code", "password") .accessTokenValiditySeconds(24 * 3600) .scopes("all"); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //允许客户端发送表单来进行权限认证来获取令牌 //只允许认证的客户端,比如网关服务才可以获取和校验token security.allowFormAuthenticationForClients() .checkTokenAccess("isAuthenticated()") .tokenKeyAccess("isAuthenticated()"); } }

网关服务

全局过滤器拦截并处理请求。流行网关对比。网关的理解: 系统网关+业务网关。

基础配置

新建house-gateway服务,引入spring cloud gateway 依赖。 spring-boot-starter-web和gateway冲突,需要去除。 <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.babyjuan</groupId> <artifactId>house-common</artifactId> <version>${house.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency> </dependencies> bootstrap.properties初始化依赖的nacos配置。 spring.application.name=house-gateway spring.profiles.active=gateway-application spring.cloud.nacos.config.namespace=${HOUSE_ENV} spring.cloud.nacos.config.group=house spring.cloud.nacos.config.server-addr=${NACOS_ADDR} spring.cloud.nacos.config.prefix=house spring.cloud.nacos.config.file-extension=yaml nacos的house-gateway-application.yaml中 配置端口为9080。获取token的请求(/oauth/token开头)转发到鉴权服务器。业务请求转发到业务网关。 server: port: 9080 service: url: house: http://localhost:8080 oauth: http://localhost:6001 spring: cloud: gateway: routes: # 路由的ID - id: oauth # 匹配后路由地址 uri: ${service.url.oauth} predicates: - Path=/oauth/token # 路由的ID - id: house # 匹配后路由地址 uri: ${service.url.house} predicates: - Path=/house/**

网关过滤器配置

请求头中赋予网关服务合法客户端身份。对应鉴权服务AuthorizationServerConfig中gateway-client的用户名密码。 @Configuration public class GatewayClientFilter implements GlobalFilter, Ordered { private static final String GATEWAY_CLIENT_AUTHORIZATION = "Basic " + Base64.getEncoder().encodeToString("gateway-client:123456".getBytes()); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //网关身份 ServerHttpRequest.Builder builder = exchange.getRequest().mutate(); builder.header("Authorization", GATEWAY_CLIENT_AUTHORIZATION); return chain.filter(exchange.mutate().request(builder.build()).build()); } @Override public int getOrder() { return 1; } }

权限校验过滤器配置

对需要鉴权的url,调用鉴权服务校验身份,通过则网关转发,否则直接返回。 @Configuration public class AccessGatewayFilter implements GlobalFilter, Ordered { @Autowired private AuthClient authClient; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String url = request.getPath().value(); if (authClient.hasPermissionControl(url)) { if (authClient.accessable(request)) { return chain.filter(exchange); } return unauthorized(exchange); } return chain.filter(exchange); } private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) { serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); DataBuffer buffer = serverWebExchange.getResponse() .bufferFactory().wrap(HttpStatus.UNAUTHORIZED.getReasonPhrase().getBytes()); return serverWebExchange.getResponse().writeWith(Flux.just(buffer)); } @Override public int getOrder() { return 2; } } token从url的请求参数获取。校验token的鉴权服务url后缀为/oauth/check_token。 @Component public class AuthClient { private static final Logger LOGGER = LoggerFactory.getLogger(AuthClient.class); private RestTemplate restTemplate = new RestTemplate(); @Value("#{'${service.url.oauth}'+'/oauth/check_token'}") private String checkTokenUrl; public boolean hasPermissionControl(String url) { return url.startsWith("/house"); } public boolean accessable(ServerHttpRequest request) { String token = request.getQueryParams().getFirst("token"); UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(checkTokenUrl).queryParam("token", token); URI url = builder.build().encode().toUri(); HttpEntity<?> entity = new HttpEntity<>(request.getHeaders()); try { ResponseEntity<TokenInfo> response = restTemplate.exchange(url, HttpMethod.GET, entity, TokenInfo.class); LOGGER.info("oauth request: {}, response body: {}, reponse status: {}", entity, response.getBody(), response.getStatusCode()); return response.getBody() != null && response.getBody().isActive(); } catch (RestClientException e) { LOGGER.error("oauth failed.", e); } return false; } }

接口调试

从网关获取token。带token从网关访问业务通过鉴权。

前端适配

登录时从后端获取token,暂存在store和cookies中。 const actions = { // user login login({ commit }, userInfo) { const { username, password } = userInfo return new Promise((resolve, reject) => { login({ username: username.trim(), password: password, grant_type: "password" }).then(response => { const data = response commit('SET_TOKEN', data.access_token) commit('SET_NAME', username.trim()) setToken(data.access_token) resolve() }).catch(error => { reject(error) }) }) }, } const mutations = { SET_TOKEN: (state, token) => { state.token = token }, SET_NAME: (state, name) => { state.name = name } } export function setToken(token) { return Cookies.set(TokenKey, token) } axios拦截器获取store中的token,拼接在查询参数里。 service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token) { let params = config.params; if (params == null) { params = {}; } params.token = getToken(); config.params = params; } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) export function getToken() { return Cookies.get(TokenKey) }
最新回复(0)