零基础学习SpringSecurity OAuth2 四种授权模式(理论+实战)(配套视频讲解)
配套视频直达
背景
前段时间有同学私信我,让我讲下Oauth2授权模式,并且还强调是零基础的那种,我也不太理解这个零基础到底是什么程度,但是我觉得任何阶段的同学看完我这个视频,对OAuth2的理解将会有很大的提升,并且也会熟练的使用SpringSecurity OAuth2,轻松搭建认证服务和资源服务。
大概了解下 SpringSecurity
Spring Security是一套安全框架,可以基于RBAC(基于角色的权限控制)对用户的访问权限进行控制,核心思想是通过一套filterChanin进行拦截和过滤
再来了解OAuth2
Oauth2是一个关于授权的官方标准,核心思路是通过各种认证手段(需要用户自己选择实现)进行认证用户身份,并颁发token,使得第三方应用可以使用该令牌在限定时间和限定范围内访问指定资源
再来了解SpringSecurity Oauth2
Spring Security OAuth2建立在Spring Security的基础之上,实现了OAuth2的规范
概念部分铺垫完成了,现在我们讲下OAuth2的四种授权模式
四种授权模式
为什么提供四种呢?
四种授权模式代表OAuth授权第三方的不同互信程度
授权码模式(最安全,也最常用)
通过授权码获取token
场景
当用户使用B网站的账号(如微信)授权登陆A网站
步骤
- 跳转到B网站授权页面,输入B网站用户名和密码
- B网站认证服务验证通过,回调网站A提供的回调地址,并带上认证code
- A网站收到回调之后,提取code,再调用B网站获取token的接口,并带上A网站在B网站上面注册的client_id和client_secert
别说话,看图
简化模式(有中间人攻击的风险)
简化模式在授权码模式的基础上减去了获取授权码的步骤,B网站认证通过之后,直接返回token 回调给A网站,所以又称为(授权码)“隐藏式”(implicit)
步骤
- 跳转到B网站授权页面,输入B网站用户名和密码
- B网站认证服务验证通过,回调网站A提供的回调地址,并带上token信息
看图
密码模式(除非没得选才用,毕竟涉及到用户隐私)
A网站要求用户直接输入用户名和密码,A网站自己拿着用户的密码和账号去B网站进行认证,直接获取token
步骤
- A网站要求用户输入账号和密码
- A网站携带账号和密码去B网站认证
- B网站认证通过,直接返回token给A网站
看图
客户端模式
客户端模式其实和用户就没关系了,其实也可以说它不属于Oauth了,因为是用网站A自己的身份信息去B网站认证获取token,A网站上的所有用户去访问B网站的资源,都是用的A网站自己的客户端信息。
场景
这个一般用于微服务之间进行认证,如服务A想访问服务B,那么服务B需要校验服务A有没有权限访问自己,所以就需要校验服务B的token,这时就可以采用客户端模式,后面讲资源服务去认证中check_toekn的时候,用的就是这种方式。
看图
现在四种授权模式讲完了,大家是不是有个疑问:
前面三种授权方式,拿到token之后,是怎么和用户进行绑定的呢?
接下来看下实战过程中,是怎么用SpringSecurity Oauth2 实现四种授权方式。
准备好电脑,开干
创建认证服务
创建ClientDetailsServiceImpl
根据clientID获取客户端信息,security会去调用多次,所以一般加上缓存
package com.lglbc.oauth2.config.details.client;import com.lglbc.oauth2.entity.OauthClient;
import com.lglbc.oauth2.enums.PasswordEncoderTypeEnum;
import com.lglbc.oauth2.service.OauthClientService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;import java.util.Objects;/*** @author: 乐哥聊编程(全平台同号)*/
@Service
@RequiredArgsConstructor
public class ClientDetailsServiceImpl implements ClientDetailsService {private final OauthClientService oauthClientService;@Overridepublic ClientDetails loadClientByClientId(String clientId) {// 获取client信息OauthClient oauthClient = oauthClientService.lambdaQuery().eq(OauthClient::getClientId, clientId).one();if (Objects.nonNull(oauthClient)) {BaseClientDetails clientDetails = new BaseClientDetails(oauthClient.getClientId(),oauthClient.getResourceIds(),oauthClient.getScope(),oauthClient.getAuthorizedGrantTypes(),oauthClient.getAuthorities(),oauthClient.getWebServerRedirectUri());clientDetails.setClientSecret(PasswordEncoderTypeEnum.NOOP.getPrefix() + oauthClient.getClientSecret());clientDetails.setAccessTokenValiditySeconds(oauthClient.getAccessTokenValidity());clientDetails.setRefreshTokenValiditySeconds(oauthClient.getRefreshTokenValidity());return clientDetails;} else {throw new NoSuchClientException("没找到客户端信息");}}
}
创建自定义的userDetails(自定义用户自己的信息)
package com.lglbc.oauth2.config.details.user;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;/*** @author: 乐哥聊编程(全平台同号)*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SysUserDetails implements UserDetails {/*** 扩展字段*/private Long userId;/*** 默认字段*/private String username;private String password;private Boolean enabled;private Collection<SimpleGrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return this.enabled;}
}
创建根据用户账号获取信息的detailService
package com.lglbc.oauth2.config.details.user;import com.lglbc.oauth2.entity.Role;
import com.lglbc.oauth2.entity.User;
import com.lglbc.oauth2.enums.PasswordEncoderTypeEnum;
import com.lglbc.oauth2.mapper.UserRoleMapper;
import com.lglbc.oauth2.service.RoleService;
import com.lglbc.oauth2.service.UserService;
import com.xiaoleilu.hutool.collection.CollectionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Service;import java.util.*;
import java.util.stream.Collectors;@Service
public class SysUserDetailServiceImpl implements UserDetailsService {@Autowiredprivate UserService userServiceImpl;@Autowiredprivate RoleService roleServiceImpl;@Autowiredprivate UserRoleMapper userRoleMapper;@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {Map<String, Object> map = new HashMap<>();map.put("username", name);User user = userServiceImpl.lambdaQuery().eq(User::getUsername, name).one();if (user == null) {throw new UsernameNotFoundException("此用户不存在");}Collection<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<>();List<String> roleIds = userRoleMapper.getRoleIdByUserId(user.getId());if (CollectionUtil.isNotEmpty(roleIds)){List<Role> roles = roleServiceImpl.lambdaQuery().in(Role::getId, roleIds).list();List<String> roleCodes = roles.stream().map(Role::getCode).collect(Collectors.toList());for (String roleCOde : roleCodes) {SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+roleCOde);grantedAuthorities.add(grantedAuthority);}}return new SysUserDetails(user.getId(), user.getUsername(),PasswordEncoderTypeEnum.BCRYPT.getPrefix() + user.getPassword(),true, grantedAuthorities);}}
创建认证服务配置
package com.lglbc.oauth2.config;import com.lglbc.oauth2.config.details.client.ClientDetailsServiceImpl;
import com.lglbc.oauth2.config.details.user.SysUserDetails;
import com.xiaoleilu.hutool.collection.CollectionUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;import java.security.KeyPair;
import java.util.*;/*** @author: 乐哥聊编程(全平台同号)*/
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {private final AuthenticationManager authenticationManager;private final ClientDetailsServiceImpl clientDetailsService;// 该对象用来将令牌信息存储到Redis中private final RedisConnectionFactory redisConnectionFactory;/*** OAuth2客户端*/@Override@SneakyThrowspublic void configure(ClientDetailsServiceConfigurer clients) {clients.withClientDetails(clientDetailsService);}/*** 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {// Token增强TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();List<TokenEnhancer> tokenEnhancers = new ArrayList<>();tokenEnhancers.add(tokenEnhancer());tokenEnhancers.add(jwtAccessTokenConverter());tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);// 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);endpoints.authenticationManager(authenticationManager).accessTokenConverter(jwtAccessTokenConverter()).tokenEnhancer(tokenEnhancerChain).tokenGranter(compositeTokenGranter).tokenServices(tokenServices(endpoints));}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.checkTokenAccess("permitAll()");}public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();List<TokenEnhancer> tokenEnhancers = new ArrayList<>();tokenEnhancers.add(tokenEnhancer());tokenEnhancers.add(jwtAccessTokenConverter());tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);DefaultTokenServices tokenServices = new DefaultTokenServices();tokenServices.setTokenStore(endpoints.getTokenStore());tokenServices.setSupportRefreshToken(true);tokenServices.setReuseRefreshToken(true);tokenServices.setClientDetailsService(clientDetailsService);tokenServices.setTokenEnhancer(tokenEnhancerChain);return tokenServices;}/*** JWT内容增强*/@Beanpublic TokenEnhancer tokenEnhancer() {return (accessToken, authentication) -> {Map<String, Object> additionalInfo = CollectionUtil.newHashMap();if (Objects.nonNull(authentication.getUserAuthentication())) {Object principal = authentication.getUserAuthentication().getPrincipal();if (principal instanceof SysUserDetails) {SysUserDetails sysUserDetails = (SysUserDetails) principal;additionalInfo.put("userId", sysUserDetails.getUserId());additionalInfo.put("username", sysUserDetails.getUsername());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);}}return accessToken;};}/*** 使用非对称加密算法对token签名*/@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setKeyPair(keyPair());return converter;}/*** 密钥库中获取密钥对(公钥+私钥)*/@Beanpublic KeyPair keyPair() {KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());KeyPair keyPair = factory.getKeyPair("jwt", "123456".toCharArray());return keyPair;}}
创建安全配置
package com.lglbc.oauth2.config;import com.lglbc.oauth2.common.result.R;
import com.lglbc.oauth2.common.result.ResultCode;
import com.xiaoleilu.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;/*** @author: 乐哥聊编程(全平台同号)*/
@Configuration
@EnableWebSecurity
@Slf4j
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private final UserDetailsService sysUserDetailsService;private final String realName = ".";@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/oauth/**", "/test/**", "/login").permitAll().anyRequest().authenticated().and().httpBasic().and().csrf().disable().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());}/*** 认证管理对象** @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 添加自定义认证器** @param auth*/@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(daoAuthenticationProvider());}/*** 设置默认的用户名密码认证授权提供者** @return*/@Beanpublic DaoAuthenticationProvider daoAuthenticationProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService(sysUserDetailsService);provider.setPasswordEncoder(passwordEncoder());provider.setHideUserNotFoundExceptions(false); // 是否隐藏用户不存在异常,默认:true-隐藏;false-抛出异常;return provider;}@Beanpublic PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}/*** 自定义认证异常响应数据*/@Beanpublic AuthenticationEntryPoint authenticationEntryPoint() {return (request, response, e) -> {if (e.getMessage().equals("User must be authenticated with Spring Security before authorization can be completed.")) {response.addHeader("WWW-Authenticate", "Basic realm= " + realName);response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());} else {response.setStatus(HttpStatus.OK.value());response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Cache-Control", "no-cache");R result = R.failed(ResultCode.CLIENT_AUTHENTICATION_FAILED);response.getWriter().print(JSONUtil.toJsonStr(result));response.getWriter().flush();}};}
}
重写token enpoint
package com.lglbc.oauth2;import com.lglbc.oauth2.common.result.R;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;import java.security.KeyPair;
import java.security.Principal;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;/*** @author: 乐哥聊编程(全平台同号)*/
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
@Slf4j
public class AuthController {private KeyPair keyPair;private final TokenEndpoint tokenEndpoint;@PostMapping("/token")public Object postAccessToken(Principal principal,@RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {OAuth2AccessToken accessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();return R.ok(accessToken);}@GetMapping("/public-key")public Map<String, Object> getPublicKey() {RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAKey key = new RSAKey.Builder(publicKey).build();return new JWKSet(key).toJSONObject();}
}
演示四种授权模式
授权码模式
获取授权码
- 浏览器打开:http://localhost:8080/oauth/authorize?response_type=code&client_id=admin
- 输入用户名和密码
- 点击授权,获取授权码
获取token
curl --location --request POST --X POST 'http://localhost:8080/oauth/token?code=k9jcxv&grant_type=authorization_code' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--header 'Authorization: Basic YWRtaW46YW1z'
刷新token
curl --location --request POST --X POST 'http://localhost:8080/oauth/token?refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhbXMiLCJzY29wZSI6WyJ3cml0ZSJdLCJhdGkiOiI4NjZmYWY2MC05MTE1LTQ0NGMtOWNkNi05MjRmYTRlZjQ4YjUiLCJleHAiOjE2NTQ1MzExMzcsInVzZXJJZCI6NCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiIyNDg1M2NiNi02ZGM1LTRlMDQtYjAxOC0wMGE0MGMxNGMyNjAiLCJjbGllbnRfaWQiOiJhZG1pbiIsInVzZXJuYW1lIjoiYW1zIn0.BX87xbC1SEVgXsQ6qdI1_RLVOY1ffqoKDvd-Fnu5FfJor6CR-3NtUjMBIQig_jAnJwptG8ZIs1q715GAQTtgoRVUMsnXBv8RKmlDvxjclUYIWrRXy1d9Rj84WC4niDU8rOzazr4L1-chkwod3rAEDAZSiBcd4XZ1S2cfG7WBSH1TcivNtY0pk_ePS08ItN_2wYm57-7_JD8XCRecpn6nF6ax20ysimqPftAKIiVzvhhN4O_TsHd3-lB7ZYTsT-SlzjBu43gm5eGFWhsOBK7NgxObwoeXaoARrzW3GaUw8DsLTKsDofyIIFGnKee9sooAwejPrsxtVRJlfj8gIc5EdA&grant_type=refresh_token' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--header 'Authorization: Basic YWRtaW46YW1z'
简单模式
- 浏览器访问:http://localhost:8080/oauth/authorize?response_type=token&client_id=admin
- 输入用户名和密码(用户信息,不是客户端)
- 点击授权,直接获取token
用户名密码模式
curl --location --request POST --X POST 'http://localhost:8080/oauth/token?grant_type=password&username=ams&password=ams' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--header 'Authorization: Basic YWRtaW46YW1z'
客户端模式
curl --location --request POST --X POST 'http://127.0.0.1:8080/oauth/token?grant_type=client_credentials' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--header 'Authorization: Basic YWRtaW46YW1z'
创建资源服务
资源服务配置
package com.lglbc.oauth2.config;import com.fasterxml.jackson.databind.ObjectMapper;
import com.lglbc.oauth2.common.KeyPairUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.client.RestTemplate;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
import java.util.stream.Collectors;@Configuration
public class Oauth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter {private static final String CHECK_TOKEN_URL = "http://localhost:8080/oauth/check_token";// 也可以使用认证服务去校验
// @Override
// public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// RemoteTokenServices tokenService = new RemoteTokenServices();
// tokenService.setCheckTokenEndpointUrl(CHECK_TOKEN_URL);
// tokenService.setClientId("resource");
// tokenService.setClientSecret("resource");
// resources.tokenServices(tokenService);
// }@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();//设置用于解码的非对称加密的公钥converter.setVerifierKey(KeyPairUtil.getPubKey());return converter;}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 任何请求都必须具有all这个域的授权.antMatchers("/test/read").access("#oauth2.hasScope('read') || #oauth2.hasScope('write')").antMatchers("/test/write").access("#oauth2.hasScope('write')").and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}
}
创建安全配置,添加拦截
package com.lglbc.oauth2.config;import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/test/**").authenticated();// 禁用CSRFhttp.csrf().disable();}
}
演示资源服务
定义两个接口
- /read:需要有admin角色 并且有对目前服务的读权限
@RequestMapping("/read")@PreAuthorize("hasAuthority('ROLE_ADMIN')")public String read(){System.out.println("read");return "read";}
- /write:需要有admin角色 并且有对目前服务的读写权限
@RequestMapping("/write")
// @Secured({"ROLE_ADMIN"})@PreAuthorize("hasAuthority('ROLE_ADMIN')")public String write() throws ParseException {return UserContextUtil.getUserId()+""+UserContextUtil.getUserName();}
常见问题
角色权限注解问题
- 不加ROLE_前缀,只能使用此注解
@PreAuthorize(“hasAuthority(‘ADMIN2’)”)
- 加ROLE_前缀,可以使用如下注解:
@PreAuthorize(“hasRole(‘ADMIN’)”) //允许
@PreAuthorize(“hasRole(‘ROLE_ADMIN’)”) //允许
@PreAuthorize(“hasAuthority(‘ROLE_ADMIN’)”) //允许
客户端密钥问题
- 如果密钥没有加密,在构建clientDetail的时候一定要加上{noop}前缀
- 否则加上 {bcrypt}前缀
持续整理…
获取用户信息
源码部分
由于在资源服务中,我们没有重写userDetailService,所以我们是获取不到用户的信息(我们也没必要写),但是我们可以自己解析token,拿到用户信息
package com.lglbc.oauth2.config;import com.lglbc.oauth2.common.UserContextUtil;
import com.nimbusds.jose.JWSObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;public class UserInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String authorization = request.getHeader("Authorization");if (StringUtils.isBlank(authorization)) {return true;}String token = StringUtils.substringAfter(authorization, "Bearer ");if (StringUtils.isBlank(token)) {return true;}try {Map<String, Object> stringObjectMap = JWSObject.parse(token).getPayload().toJSONObject();UserContextUtil.setUser(stringObjectMap);} catch (Exception e) {}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserContextUtil.removeUser();}
}
源码领取: https://mp.weixin.qq.com/s/XTkss0hDPMm8YsA0fM6YNA
零基础学习SpringSecurity OAuth2 四种授权模式(理论+实战)(配套视频讲解)相关推荐
- Spring Security+Oauth2四种授权模式
上一篇文章:Spring Security + OAuth2.0项目搭建:https://blog.csdn.net/qq_42402854/article/details/123057625 接着认 ...
- SpringBoot Security的oauth2四种授权模式使用
oauth2四种使用方式 密码模式 localhost:8080/oauth/token?client_id=client_id_1&client_secret=123456&gran ...
- Outh2协议有哪四种授权模式?
文章目录 1. OAuth2介绍 2. OAuth2的四种授权模式 ①:授权码模式 ②:密码模式 ③:简化(隐式)模式 ④:客户端模式 3. token的携带方式 4. token的自动续期 refr ...
- Oauth原理和四种授权模式
Oauth四种授权模式 1.oauth授权码模式 用户访问应用前端页面(验证是否登录). 访问的页面将请求重定向到认证服务器. 认证服务器等待用户授权(输入账号.密码). 用户授权,认证服务器验证(C ...
- SpringSecurity OAuth2四种模式说明
OAuth2.0中四种授权方式 授权码模式(authorization code) 流程 说明:[A服务客户端]需要用到[B服务资源服务]中的资源 第一步:[A服务客户端]将用户自动导航到[B服务认证 ...
- oauth2四种授权方式小结
序 本文主要小结一下oauth2的四种模式的特点和适用场景. 四种授权方式 OAuth 2.0定义了四种授权方式. 密码模式(resource owner password credentials) ...
- 什么是Oauth2.0授权,四种授权模式
文章目录 什么是OAuth2.0? 1. OAuth2四种模式 1.1. 隐式授权模式(Implicit Grant) 1.2. 授权码授权模式(Authorization code Grant) 1 ...
- OAuth2四种认证模式
什么是OAuth2 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAut ...
- SpringSecurity OAuth2.0认证授权-part2
此篇文章包含oauth2项目搭建.整合jwt.授权方式测试: 篇幅过长,拆分为: part1: 认证授权原理回顾及分布式系统认证方案: part2: oauth2项目搭建.授权方式测试: part3: ...
最新文章
- 哈啰程序员吐槽:试用期带5个人创造了部门历史最高成绩,结果却被辞退
- python No tests were found问题解决方法
- 写一个不能被继承的类(友元的不可继承性)
- UVa1316 Supermarket(贪心)
- Subversion 1.7 Eclipse integration in Ubuntu12(转载)
- oracle表格颜色,如何在oracle中使用光标更新特定颜色
- TURBOLinux 7.0下安装Oracle 8.1.7.0.1 release 3
- AndroidService 深度分析(2)
- Oracle数据库案例整理-Oracle系统执行时故障-Shared Pool内存不足导致数据库响应缓慢...
- ArrayList的四种初始化方法
- 45 张图深度解析 Netty 架构与原理
- 问题六十二:怎么求一元十次方程在区间内的所有不相等的实根(2)——修正“区间端点零值”问题
- 查看php-fpm进程数_查看php-fpm开启的进程数以及每个进程的内存限制
- WebsResponse
- php自动加载 依赖,php自动加载
- Luogu 3206 [HNOI2010]城市建设
- DAO包的作用设计和命名
- mysql_union all 纵向合并建表_20170123
- 最牛逼的心理学家是如何看待人生的
- ph1 android p,ph1(安卓之父ph1参数)