忘记po源码了,点这里[github 源码]
开篇当然是包结构啦。
@EnableAuthorizationServer
@SpringBootApplication
( scanBasePackages = {“com.qiyun.qy”}, exclude = {
DataSourceAutoConfiguration.class }) //由于是多数据源,所以,屏蔽默认数据源设置
这一段在spring boot 1.59版本不需要写。当你有了自己的数据源配置后,spring boot会自动采用你自定义的配置。
也正是由于我多加了这段exclude导致@ResponseBody无法正确返回。而我以为是缺少了jpa jar。
public class QyApplication {
public static void main(String[] args) {
SpringApplication.run(QyApplication.class, args);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("qiYunSecurityProvider")
private AuthenticationProvider provider;// 自定义验证
@Autowired
@Qualifier("qiYunUserService")
private UserDetailsService userService; //查询用户
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()//禁用csrf (csrf会拦截所有post请求)
.headers().frameOptions().disable()//允许使用iframe
.and().authorizeRequests()
.antMatchers("/home", "/register", "/socket").permitAll()//首页、注册、web socket,不需要权限
.antMatchers("/static/**", "/ueditor/**", "/error/**").permitAll()
.antMatchers("/oauth/**", "/api/**").permitAll()//当前filter不拦截 OAuth2.0 的路径
.anyRequest().authenticated()
.antMatchers("/admin/**").hasRole("admin")//预留管理员权限
.and().formLogin().defaultSuccessUrl("/home").permitAll()
.and().logout().logoutUrl("/login?logout").logoutSuccessUrl("/").permitAll();
}
@Autowired
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider);
}
}
@Service(“qiYunSecurityProvider”)
public class SecurityProvider implements AuthenticationProvider {
@Autowired
@Qualifier("qiYunUserService")
private UserDetailsService userService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String rawPassword = authentication.getCredentials().toString();
User user = (User) userService.loadUserByUsername(name);
String sqlPassword = user.getPassword();
//加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (!encoder.matches(rawPassword, sqlPassword)) {
throw new BadCredentialsException("用户名或密码错误");
}
List grantedAuths = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(name, sqlPassword, grantedAuths);
}
@Override
public boolean supports(Class> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
@Service(“qiYunUserService”)
public class UserService implements UserDetailsService {
@Autowired
IUserDao userDao;
@Override
public User loadUserByUsername(String name)
throws UsernameNotFoundException {
User user = userDao.findByUserKey(name);
if (user == null) {
throw new UsernameNotFoundException("用户不存在!");
}
return user;
}
}
@Configuration
@EnableAuthorizationServer // 认证服务器注解
@EnableResourceServer //资源服务器注解
public class ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("baseDataSource")
private DataSource dataSource;
@Autowired
@Qualifier("qiYunClientService")
private ClientDetailsService clientService;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()//允许表单登录
.passwordEncoder(new BCryptPasswordEncoder());//client_secret的加密方式
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).clients(clientService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore());//使用内存中的 token store
//.authorizationCodeServices(
//new JdbcAuthorizationCodeServices(dataSource));
//使用Jdbctoken store
//.pathMapping("/oauth/token", "/oauth2/token");//自定义授权提交页面
}
@Bean
public TokenStore tokenStore(){
return new InMemoryTokenStore(); //使用内存中的 token store
//return new JdbcTokenStore(dataSource); ///使用Jdbctoken store
}
}
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/api/**")//仅拦截api路径,与用户认证隔离
.and().authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/api/**").access("#oauth2.hasScope('write')");
}
}
@Service(“qiYunClientService”)
public class ClientService implements ClientDetailsService {
@Autowired
private IClientDao clientDao;
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
Client client = clientDao.loadClientByClientId(clientId);
return client;
}
}
至此,spring security的用户认证和OAuth2的客户端授权,已整合完成。
在这里点出几个坑:
1.不指定client_secret的加密方式,
那么spring boot会采用默认的BCryptPasswordEncoder()。
如果此时你数据库里的client_secret字段值未加密,
恭喜你,会报加密错误。
(ps:当时忙着搭框架,没截图。有兴趣的,可以自己尝试)
2.获取accesss_token时,不缺少jackson的相关jar包,
却报错 no converter。本以为是源码里的ResponseEntiry导致的。
后来测试@ResponseBody也报错,才发现是框架打错了。
检查了一遍pom,发现是spring-boot-starter-data-jpa干掉了。
添加上依赖,OK。
【更正】:不是因为缺少了jpa的jar包,而是因为app入口上的类排除导致的。
3.关于客户端授权链接“/oauth/authorize?
client_id=2&client_secret=secret&
response_type=code
&scope=read write trust
&redirect_uri=http://127.0.0.1/home”
看网上都是用逗号分割scope。自己用逗号总报错。
看完源码,发现源码采用的分隔符是空格。
所以,请求链接中也需要用空格分割。
至于数据库中,可以用逗号,
然后在自己的ClientDetails类的getScope()中,
将字符串转换成Set就行