REST全称是Representational State Transfer; REST指的是一组架构约束条件和原则。有兴趣了解的朋友参考RESTful 架构详解;
@EnableAuthorizationServer: 用于激活OAuth 2.0授权服务器。
主要配置: 客户端信息, 管理令牌, 授权类型。
实现接口: AuthorizationServerConfigurer。
或者继承AuthorizationServerConfigurerAdapter。
其实查看AuthorizationServerConfigurerAdapter发现其本身就实现了AuthorizationServerConfigurer。
void configure(AuthorizationServerSecurityConfigurer security) throws Exception;
void configure(ClientDetailsServiceConfigurer clients) throws Exception;
void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;
具体配置例子:
/**
* @Date 2019年5月21日
* @Sgin AuthorizationServerConfig--认证服务核心配置
* @Author Bertram.Wang
*/
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource(name = "dsOauth")
DataSource dsOauth;
@Autowired
RedisConnectionFactory connectionFactory;
@Autowired
MyUserDetailsService userDetailsService;
@Autowired
AuthenticationManager authenticationManager;
/**
* 设置令牌存储方式
* InMemoryTokenStore 在内存中存储令牌。
* RedisTokenStore 在Redis缓存中存储令牌。
* JwkTokenStore 支持使用JSON Web Key (JWK)验证JSON Web令牌(JwT)的子Web签名(JWS)
* JwtTokenStore 不是真正的存储,不持久化数据,身份和访问令牌可以相互转换。
* JdbcTokenStore 在数据库存储,需要创建相应的表存储数据
*/
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(connectionFactory);
}
/**
* 设置密码校验器
* NoOpPasswordEncoder 直接文本比较 equals
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* *用来配置令牌端点(Token Endpoint)的安全约束。
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();//允许客户表单认证
security.passwordEncoder(passwordEncoder());//设置oauth_client_details中的密码编码器
security.checkTokenAccess("permitAll()");//对于CheckEndpoint控制器[框架自带的校验]的/oauth/check端点允许所有客户端发送器请求而不会被Spring-security拦截
}
/**
* *配置OAuth2的客户端相关信息。使用了数据库存储
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// jdbcTemplate 会查询指定数据源表: oauth_client_details;
clients.jdbc(dsOauth);
}
/**
* *配置授权服务器端点的属性和增强功能。
* *设置自定义验证规则, token存储设置使用...
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.userDetailsService(userDetailsService)
// refreshToken是否可以重复使用。 默认:true;
.reuseRefreshTokens(false);
}
}
注意:
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean(name = "authenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
刷新token时,refreshToken有效时间更新,实现一次登录一直使用。
reuseRefreshTokens 设置为false。
MyUserDetailsService:自定义的身份验证逻辑实现接口 UserDetailsService
示例:
/**
* @Date 2019年5月21日
* @Sgin MyUserDetailsService
* @Author Bertram.Wang
*/
@Component
public class MyUserDetailsService implements UserDetailsService {
private static final Logger log = LoggerFactory.getLogger(MyUserDetailsService.class);
@Autowired
private MemberRepository memberRepository;
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String clientFlagValue;
String name;
try {
// username 使用客户端ID凭借 == clientId:username
String[] split = username.split(":");
clientFlagValue = split[0];
name = split[1];
} catch (Exception e) {
log.error("用户名请拼接资源服务标识");
return null;
}
MyUserDetails userDetails = new MyUserDetails();
userDetails.setUsername(username);
// 根据标识区分具体的客户端
OauthClientFlagEnum clientFlagEnum = OauthClientFlagEnum.build(clientFlagValue);
switch (clientFlagEnum) {
case VUE_WEB:
User user = userRepository.oneByName(name);
if (user == null) {
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
}
userDetails.setUserId(user.getId().toString());
userDetails.setPassword(user.getPassword());
break;
case MEMBER_API:
Member member = memberRepository.oneByPhone(name);
if (member == null) {
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
}
userDetails.setUserId(member.getId().toString());
userDetails.setPassword(member.getPassword());
break;
default:
log.error("用户名请拼接资源服务标识==clientFlagValue:{}", clientFlagValue);
return null;
}
return userDetails;
}
/**
* *用于存储用户信息
*/
@Data
private final static class MyUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String userId;
private MyUserDetails() {
super();
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
Role role = new Role();
return role.getRoles();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
}
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
首先配置所有连接都可以访问。
@Configuration
// 注解开启Spring Security的功能
@EnableWebSecurity
// 开启自动配置
@EnableAutoConfiguration
// 启用了一个Oauth2 客户端配置
@EnableOAuth2Client
public class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 禁用csrf支持
// 允许使用 HttpServletRequest 限制访问
.authorizeRequests()
// 任何人都可以使用任何URL资源
.antMatchers("/**").permitAll();
}
}
添加自定义拦截器验证token
/**
* @describe 拦截器 -- oauth验权
* @author Bertram.Wang
* @date 2018年10月16日 下午8:58:17
*/
public class Oauth2Interceptor implements HandlerInterceptor {
public static final Logger LOGGER = LoggerFactory.getLogger(Oauth2Interceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String accessToken;
String authorization = request.getHeader("Authorization");
if (StringUtils.isEmpty(authorization)) {
ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
return false;
}
try {
accessToken = new String(Base64.getDecoder().decode(authorization)).split(":")[1];
} catch (Exception e) {
LOGGER.error(e.getMessage());
ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
return false;
}
OAuth2AccessToken oauth2AccessToken = Oauth2Utils.checkTokenInOauthClient(accessToken);
if (oauth2AccessToken == null) {// 非法的Token值
ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
return false;
} else if (oauth2AccessToken.isExpired()) {// token失效
ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
return false;
}
Map additionalInformation = oauth2AccessToken.getAdditionalInformation();
int memberId = Integer.parseInt(additionalInformation.get("userId").toString());
request.setAttribute("memberId", memberId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
protected static String extractTokenKey(String value) {
if (value == null) {
return null;
}
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).");
}
try {
byte[] bytes = digest.digest(value.getBytes("UTF-8"));
return String.format("%032x", new BigInteger(1, bytes));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).");
}
}
}
注册拦截器
/**
* @describe WEBMVC配置
* @author Bertram.Wang
* @date 2018年10月16日 下午8:56:49
*/
@Configuration
public class InterceptorRegisterConfiguration implements WebMvcConfigurer{
@Bean
public Oauth2Interceptor oauth2Interceptor() {
return new Oauth2Interceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(oauth2Interceptor())
// 排除指定连接不做拦截
.excludePathPatterns("/swagger-resources/**",
"/webjars/**",
"/swagger-ui.html/**",
"/docs",
"/account/**",
"/auth/**" );
}
}
完成OAuth身份验证。