一、密码模式(User Credentials)简介
密码模式(resource owner password credentials)(为遗留系统设计)(支持refresh token)
授权码模式(authorization code)(正宗方式)(支持refresh token)
简化模式(implicit)(为web浏览器应用设计)(不支持refresh token)
客户端模式(client credentials)(为后台api服务消费者设计)(不支持refresh token)
完整的项目结构分为:客户端服务,认证服务,资源服务。客户端需要访问资源服务的资源时,则需要得到认证服务的认证。
密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。
这种模式是最不推荐的,因为client可能存了用户密码。
这种模式主要用来做遗留项目升级为oauth2的适配方案。
在这种模式中,用户必须把自己的密码
给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
(A)用户向客户端提供用
户名和密码
。
(B)客户端将用户名和密码
发给认证服务器,向后者请求令牌。
客户端发出的HTTP请求,包含以下参数:
grant_type:表示授权类型,此处的值固定为"password",必选项。
username:表示用户名,必选项。
password:表示用户的密码,必选项。
scope:表示权限范围,可选项。
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
(C)认证服务器确认无误后,向客户端提供访问令牌。
认证服务器向客户端发送访问令牌
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
二、Maven依赖
org.springframework.cloud
spring-cloud-starter-oauth2
三、代码实现
- 实体类
登录用户名密码,
@Data
@AllArgsConstructor
public class User {
private long userId;
private String userName;
private String password;
}
将SpringUser类与自定义类进行转换:
import org.springframework.security.core.userdetails.User;
/**
* 自定义UserDetails类 携带User实例
*/
@Data
public class MyUserDetails extends User {
private com.xtsz.springbootauth2.entity.User user;
public MyUserDetails(com.xtsz.springbootauth2.entity.User user) {
super(user.getUserName(), user.getPassword(), true, true, true, true, Collections.EMPTY_SET);
this.user = user;
}
}
- 用户接口
继承Spring UserDetailsService 接口:
import org.springframework.security.core.userdetails.UserDetailsService;
public interface UserService extends UserDetailsService {
}
实现类:
@Primary
@Service("userService")
public class UserServiceImpl implements UserService {
private final static Set users = new HashSet<>();
static {
users.add(new User(1, "admin", new BCryptPasswordEncoder().encode("123456")));
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Optional user = users.stream()
.filter((u) -> u.getUserName().equals(s))
.findFirst();
if (!user.isPresent())
throw new UsernameNotFoundException("用户不能发现!");
else
return UserDetailConverter.convert(user.get());
}
private static class UserDetailConverter {
static UserDetails convert(User user) {
return new MyUserDetails(user);
}
}
}
- Security配置
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* Spring Boot 2 配置,这里要bean 注入
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
/**
* 加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- 认证服务配置
/**
* 认证服务配置
*/
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userService;
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client") // 客户端ID
.scopes("all")
.authorizedGrantTypes("password", "refresh_token") // 设置验证方式
.secret(new BCryptPasswordEncoder().encode("123456")) //必须加密
.accessTokenValiditySeconds(10000) //token过期时间
.refreshTokenValiditySeconds(10000); //refresh过期时间;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userService); //配置userService 这样每次认证的时候会去检验用户是否锁定,有效等
}
@Bean
public TokenStore tokenStore() {
// 使用内存的tokenStore
return new InMemoryTokenStore();
}
}
- 资源服务配置
/**
* 资源服务配置
*/
@Configuration
@EnableResourceServer()
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/account/**").authenticated()
.and().httpBasic()
.and().csrf().disable();
}
}
- 控制器
@RestController
@RequestMapping("/account")
@Slf4j
public class UserController {
@GetMapping("user")
public String getServer(){
return "9999服务器";
}
}
四、测试
- 访问:http://localhost:8080/account/user
-
获取令牌
五、常见问题:
1、Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
原因:JDK大于1.8。
javax.xml.bind
jaxb-api
2.3.0
com.sun.xml.bind
jaxb-impl
2.3.0
com.sun.xml.bind
jaxb-core
2.3.0
javax.activation
activation
1.1.1