spring security oauth2资源服务器的获取资源
2021-10-12 本文已影响0人
virtual灬zzZ
资源服务器配置(这里以数据库配置token方式)
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true)
public class Oauth2JdbcResourceConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "hahaRsId";
@Autowired
private DataSource dataSource;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/myoauth/**").authenticated();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenStore(jdbcTokenStore())
.stateless(true)
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler);
}
@Bean
public TokenStore jdbcTokenStore(){
return new JdbcTokenStore(dataSource);
}
获取资源过程:
先经过 OAuth2AuthenticationProcessingFilter 这个filter,到数据库验证这个access_token,token在数据库的id是经过md5加密的。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final boolean debug = logger.isDebugEnabled();
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
try {
Authentication authentication = tokenExtractor.extract(request);
if (authentication == null) {
if (stateless && isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
}
else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
Authentication authResult = authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}
catch (OAuth2Exception failed) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + failed);
}
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed));
return;
}
chain.doFilter(request, response);
}
authentication 就是起始认证的用户,不是clientSecret和clientId,而是userName和password。最后得到的权限authority也是用户的权限,不是client_detail中配置的权限,比如userName的权限是a,b,client_detail的权限是c,d,最终的权限就是a,b,
用户的权限:
client_detail的权限
验证权限过程
//正常请求
@GetMapping("/api")
@PreAuthorize("hasAuthority('测试呢5')")
public String api() {
return "my api";
}
//正常请求
@GetMapping("/test6")
@PreAuthorize("hasAuthority('测试呢6')")
public String test6() {
return "test6";
}
//权限不足,accessDeny
@GetMapping("/author1")
@PreAuthorize("hasAuthority('author1')")
public String author1() {
return "author1";
}
这里还必须注意的是
认证端和资源端分离,authentication即username这用户的类必须是一模一样的,因为数据库存的就是blob类型,里面含有class的类型,还有accessToken也必须是一模一样,在验证的过程中会解析出来,如果不一致,就是报错,而且还会删除正常的access_token,能debug出来。
比如认证端的user(实现了userDetail)
@Service("authUserDetailService")
public class AuthUserDetailService implements UserDetailsService {
@Resource(name = "userDao")
private UserDao userDao;
@Resource(name = "roleDao")
private RoleDao roleDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.selectOne(new LambdaQueryWrapper<User>().eq(User::getAccount, username));
if (user == null) {
throw new BadCredentialsException("用户[" + username + "] 没找到");
}
Integer status = user.getStatus();
if (status == 0) {
throw new DisabledException("status=0 无效用户");
}
if (status == 2) {
throw new CredentialsExpiredException("status=2 密码过期");
}
if (status == 3) {
throw new LockedException("status=3 账户已经锁定");
}
if (status == 4) {
throw new CommonAuthenticationException("status=4 CommonAuthentication,哈哈哈");
}
List<Role> roles = roleDao.selectRoleByUserId(user.getId());
Collection<GrantedAuthority> authorities = new ArrayList<>();
if (!roles.isEmpty()) {
List<String> roleNames = roles.stream().map(Role::getRoleName).collect(Collectors.toList());
for (String roleName : roleNames) {
authorities.add(new SimpleGrantedAuthority(roleName));
}
}
return new SysUser(username, user.getPassword(), authorities);
}
认证端自定义的accessToken----MyAccessToken(记得自定义序列化器),因为这里要配置成统一的json返回格式,带code和msg
@JsonSerialize(using = MyOauth2AccessTokenJsonSerializer.class)
public class MyOauth2AccessToken extends DefaultOAuth2AccessToken {
public MyOauth2AccessToken(String value) {
super(value);
}
public MyOauth2AccessToken(OAuth2AccessToken accessToken) {
super(accessToken);
}
}
public class MyOauth2AccessTokenJsonSerializer extends StdSerializer<MyOauth2AccessToken> {
protected MyOauth2AccessTokenJsonSerializer() {
super(MyOauth2AccessToken.class);
}
@Override
public void serialize(MyOauth2AccessToken token, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
CommonCodeEnum commonSuccess = CommonCodeEnum.COMMON_SUCCESS;
Result<Map> result = new Result<>(commonSuccess.getMsg(),commonSuccess.getCode());
//Map<String, Object> map = BeanUtil.beanToMap(token);
Map<String, Object> map = new HashMap<>(16);
map.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
map.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null) {
map.put(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
}
Date expiration = token.getExpiration();
if (expiration != null) {
long now = System.currentTimeMillis();
map.put(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
}
Set<String> scope = token.getScope();
if (scope != null && !scope.isEmpty()) {
StringBuilder scopes = new StringBuilder();
for (String s : scope) {
Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
scopes.append(s);
scopes.append(" ");
}
map.put(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
}
Map<String, Object> additionalInformation = token.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
map.put(key, additionalInformation.get(key));
}
result.setData(map);
jsonGenerator.writeObject(result);
}
}
{
"msg": "Success",
"code": 1000,
"data": {
"access_token": "0df06dc5-a728-4c71-83bd-43e6d77178b6",
"refresh_token": "a4a99fea-bdd1-4356-a581-ff679fba92f3",
"scope": "user_info",
"customInfo": "extra thing额外的东西",
"token_type": "bearer",
"expires_in": 10799
}
}