六号线晚报0815

Spring Security OAuth2 密码模式

官方文档
最简单应用

我的应用,主要五个类

  • 继承AuthorizationServerConfigurerAdapter,主要功能
  1. 注入authenticationManager开启密码验证模式(默认不开启)
  2. 设置tokenStore保存token,框架提供了jdbc、inmemory、redis存储方式,此处使用inmemory内存模式,实际保存在map里
  3. 注入userDetailsService设置用作验证比较的用户对象,详见下面
  4. clients设置内存保存客户端,设置客户端id和secret,设置允许的认证类型,设置授权角色,设置token有效期等
  5. endpoints设置认证管理器,设置token存储,设置加载保存的用户service
  6. 注入自己实现的PasswrodEncoder,详见下面
import com.my.service.UserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailServiceImpl userDetailService;

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore()).userDetailsService(userDetailService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("my-client-with-secret")
                .authorizedGrantTypes("password", "refresh_token")
                .authorities("ROLE_CLIENT")
//                .scopes("trust")
                .resourceIds("oauth2-resource")
                .accessTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1))
                .refreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(30))
                .secret("secret");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new RSAPasswordBCryptEncoder();
    }
}
  • 实现UserDetailsService接口,获取保存的用户用做和输入的用户密码比较

import com.my.model.AppUserAccount;
import com.my.service.UserDetailService;
import org.nutz.dao.Cnd;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Collections;


@Service
public class UserDetailServiceImpl implements UserDetailService {

    @Autowired
    private AppUserAccountServiceImpl appUserAccountServiceImpl;

    @Override
    public UserDetails loadUserByUsername(String userAccount) throws UsernameNotFoundException {

        AppUserAccount appUserAccount = appUserAccountServiceImpl.fetch(Cnd.where("user_account", "=", userAccount));
        if (appUserAccount != null) {
            User user = new User(userAccount, appUserAccount.getUserPassword(),  true, true, true, true, Collections.EMPTY_SET);
            return user;
        } else {
            throw new UsernameNotFoundException("Could not find the user '" + userAccount + "'");
        }
    }

}
  • 继承ResourceServerConfigureAdapter,设置资源访问控制

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/appuser/hello**/**").permitAll()
                .anyRequest().authenticated();
    }

}
  • 继承GlobalAuthenticationConfigurerAdapter,全局安全配置我还没有用到
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;

@Configuration
public class WebSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter {


}
  • 自定义的PasswordEncoder,解析出用户输入的明文密码和数据库的密文密码比较,这里偷懒直接调用了springsecurity提供的BCrypt哈希类

import com.wellcommsoft.util.RSA;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

public class RSAPasswordBCryptEncoder implements PasswordEncoder {
public class RSAPasswordBCryptEncoder implements PasswordEncoder {

    /**
     * @param rawPassword 客户端发送的密码,一般加密的
     * @return
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /**
     * @param rawPassword     客户端传的RSA公钥加密的密码
     * @param encodedPassword 数据库密文密码
     * @return
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        String plainPassword = RSA.decryptPrivateKey(rawPassword.toString());
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder.matches(plainPassword, encodedPassword);
    }
}

测试

  • 默认获取token的url在TokenEndPoint类里提供的/oauth/token,获取token方式如下


    六号线晚报0815_第1张图片
    postman设置认证
  • 上图TYPE选择Basic Auth,Username和Password填写上面client设置的withClient和secret作为认证客户端的clientid和secret


    六号线晚报0815_第2张图片
    Headers
  • 上图中Headers里的Authorization的值实际是填写的username和password的base64编码值,即
    Authorization = Basic+空格+base64(username-password)


    六号线晚报0815_第3张图片
    body内容
  • body里填写的scope和grant_type同样与client设置的一致,grant_type填写password,都不能为空。这里的用户密码是实际的用户账号和经过客户端加密的用户密码


    六号线晚报0815_第4张图片
    获取到的token
  • 返回的内容包括访问限制资源的token,token类型,刷新token,访问token有效时间,授权范围(注意:如果client.authorizedGrantTypes("password", "refresh_token")没有设置refresh_token则返回的内容不包含refresh_token)


    六号线晚报0815_第5张图片
    带token访问用户资源
  • 访问限制资源时候在Headers里加上Anthorization值为token_type+空格+access_token


    六号线晚报0815_第6张图片
    刷新token
  • 刷新token时Authorization和Headers里和获取token一样填写client设置的clientid和secret,body里区别是grant_type类型填写refresh_token。返回结果和获取token一致,注意access_token和refresh_token都返回的新值。

controller里获取用户账号

        String username = request.getUserPrincipal().getName();

退出登录

 @Autowired
    private ConsumerTokenServices consumerTokenServices;

@DeleteMapping("/logout")
    public Result logout() {
        String authorization = request.getHeader("Authorization");
        if (!StringUtil.strIsNullOrEmpty(authorization)) {
            String accessToken = authorization.split(" ")[1];
            boolean removeToken = consumerTokenServices.revokeToken(accessToken);
            if (removeToken) {
                return ResultUtils.success();
            }
        }
        return ResultUtils.error();
    }

你可能感兴趣的:(六号线晚报0815)