没有网关的微服务认证中心
2019-12-18 本文已影响0人
_Rondo
一、前言
最近在写微服务认证中心,实现了基本功能,然而到了网关代理请求过去总是未授权,不知道什么原因,先记下来有空再完善。
二、实例
parent pom
<dependencyManagement>
<dependencies>
<!-- cloud 组件版本 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
common pom 引入security
<!--cloud oauth2 服务认证 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
我把jwt 相关放到了common里
/**
* jwt token 配置类
*/
@Configuration
public class JWTTokenConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("inc-secret"); // 签名密钥
return accessTokenConverter;
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new JWTTokenEnhancer();
}
}
public class JWTTokenEnhancer implements TokenEnhancer {
/**
* 向jwt中添加额外信息
* @param oAuth2AccessToken
* @param oAuth2Authentication
* @return
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String, Object> info = new HashMap<>();
info.put("org", "chinasoftinc");
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}
注册中心就不写了,这里贴uaa pom
<dependency>
<groupId>com.chinasoftinc</groupId>
<artifactId>inc-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- eureka 里面已经包含 ribbon 了, 所以不用单独添加, ribbon依赖, 点击依赖就去看就知道了 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
认证服务器config
/**
* 授权服务配置
* @author wenx
* @version 1.0
*/
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthUserDetailService authUserDetailService;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private TokenStore jwtTokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenEnhancer tokenEnhancer;
@Resource
private DataSource dataSource;
/**
* 配置令牌token的访问端点和令牌服务
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(tokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancers);
endpoints
.authenticationManager(authenticationManager)//密码模式
.userDetailsService(authUserDetailService) //不添加的话会出错
.tokenStore(jwtTokenStore)//令牌管理服务是必须的
.tokenEnhancer(enhancerChain)
.accessTokenConverter(jwtAccessTokenConverter)
.allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许post请求访问令牌
}
/**
* 配置令牌的安全约束
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")// 开启/oauth/check_token验证端口认证权限访问
.allowFormAuthenticationForClients();//表单认证,申请令牌
}
/**
* 配置客户端详情,分配客户端标识和客户端密钥
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Bean
public AuthorizationServerTokenServices tokenServices(){
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setSupportRefreshToken(true);
services.setTokenStore(jwtTokenStore);
services.setAccessTokenValiditySeconds(7200);//令牌默认有效期2小时
services.setRefreshTokenValiditySeconds(259200);//刷新令牌有效期3天
return services;
}
@Bean // 声明 ClientDetails实现
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
// public static void main(String[] args) {
// System.out.println(new BCryptPasswordEncoder().encode("123456"));
// }
}
认证服务器webconfig
/**
* web安全配置
**/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthUserDetailService userServiceDetail;
/**
* 安全拦截机制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置密码模式服务器
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/**").authenticated()
.and()//HttpSecurity
.httpBasic()
.and()
.csrf().disable()//防csrf攻击 禁用
;
}
@Override//通过重载该方法,可配置Spring Security的Filter链(HTTP请求安全处理)
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
//设置密码加密规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceDetail).passwordEncoder(passwordEncoder());
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//这一步的配置是必不可少的,否则SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
认证服务器userdetailservice
@Slf4j
@Service
public class AuthUserDetailService implements UserDetailsService {
@Autowired
private SysUserLoginService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO 这个地方可以通过username从数据库获取正确的用户信息,包括密码和权限等。
Sys_user user = sysUserService.queryUserByName(username);
if (user == null) {
throw new BadCredentialsException("user: " + username + " not found.");
}
return new User(user.getUsername(), user.getPassword(),
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN"));
}
}
资源服务器pom
<dependency>
<groupId>com.chinasoftinc</groupId>
<artifactId>inc-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka 里面已经包含 ribbon 了, 所以不用单独添加, ribbon依赖, 点击依赖就去看就知道了 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
资源服务器配置
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String SERVER_RESOURCE_ID = "resource_1";
@Autowired
private TokenStore tokenStore;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/login","/user/register").permitAll()
.antMatchers("/**").authenticated()
;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore).resourceId(SERVER_RESOURCE_ID);
}
}
这里顺便贴一下api中的登录吧
@RestController
@Api("系统用户接口")
@RequestMapping(value = "/user",produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@Slf4j
public class SysUserController {
@Autowired
private SysUserLoginService userService;
@Autowired
private LoginUserService loginUserService;
@ApiOperation("注册方法")
@PostMapping(value = "/register")
public BaseRetBean register(@RequestParam(value = "username",required = true)String username,
@RequestParam(value = "password",required = true)String password){
BaseRetBean baseRetBean = new BaseRetBean(1,"注册成功");
if(StrUtil.isBlank(username) || StrUtil.isBlank(password)){
baseRetBean.setRet(0);
baseRetBean.setMsg("用户或密码不能为空!");
return baseRetBean;
}
try {
boolean bool = loginUserService.register(username,password);
if(!bool){
baseRetBean.setRet(0);
baseRetBean.setMsg("用户名已存在");
}
}catch (Exception e){
baseRetBean.setRet(-1);
baseRetBean.setMsg("注册失败");
log.debug("用户:{} 注册失败,失败原因:{}",username,e);
}
return baseRetBean;
}
@ApiOperation("登录方法")
@PostMapping(value = "/login")
public BaseRetBean login(@RequestParam(value = "username",required = true)String username,
@RequestParam(value = "password",required = true)String password){
BaseRetBean baseRetBean = new BaseRetBean(1,"登录成功");
if(StrUtil.isBlank(username) || StrUtil.isBlank(password)){
baseRetBean.setRet(0);
baseRetBean.setMsg("用户或密码不能为空!");
return baseRetBean;
}
try {
UserLoginBean userLogin = loginUserService.login(username,password);
if(userLogin == null){
baseRetBean.setRet(0);
baseRetBean.setMsg("登录失败");
}
baseRetBean.setData(userLogin);
}catch (Exception e){
baseRetBean.setRet(-1);
baseRetBean.setMsg("登录失败");
log.debug("用户:{} 登录失败,失败原因:{}",username,e);
}
return baseRetBean;
}
}
loginservice
@Service
@Slf4j
public class LoginUserService {
@Autowired
private Sys_userMapper userMapper;
@Autowired
private SysConfig sysConfig;
@Autowired
private UaaFeignService uaaFeignService;
@Autowired
private RedisUtil redisUtil;
/**
* 登录方法
* @param username
* @param password
* @return
*/
public UserLoginBean login(String username, String password){
Sys_user user = userMapper.queryUserByName(username);
if(user == null){
log.debug("用户:{} 登录查询失败",username);
return null;
}
if(!BPwdUtil.matches(password,user.getPassword())){
log.debug("用户:{} 登录密码错误",username);
}
JWT jwt = uaaFeignService.getToken(
sysConfig.getClientid(),sysConfig.getClientsecret(),
"password",username,password);
if(jwt == null){
log.debug("用户:{} token 签发错误",username);
return null;
}
//api用户bean
UserLoginBean loginBean = new UserLoginBean();
loginBean.setJwt(jwt);
user.setPassword(null);
loginBean.setUser(user);
String token = StrUtil.uuid().replace("-","");
redisUtil.set(sysConfig.getTokenkeyPrefex()+"::"+token,loginBean,24*60*60);
return loginBean;
}
public boolean register(String username,String password){
Sys_user user = userMapper.queryUserByName(username);
//用户已存在
if(user != null){
return false;
}
Sys_user adduser = new Sys_user();
BeanUtils.copyProperties(new HandleBean().post(),adduser);
adduser.setUsername(username);
adduser.setPassword(new BCryptPasswordEncoder().encode(password));
userMapper.insertSelective(adduser);
return true;
}
}
登录用到的feigncilent 和配置SysConfig
@FeignClient(value = "inc-uaa",fallback = UaaFeignServiceFullback.class)
public interface UaaFeignService {
@PostMapping("/oauth/token")
JWT getToken(@RequestParam(value = "client_id",required = true)String cilentid,
@RequestParam(value = "client_secret",required = true)String clientsecret,
@RequestParam(value = "grant_type",required = true)String type,
@RequestParam(value = "username",required = true)String username,
@RequestParam(value = "password",required = true)String password);
}
@Setter
@Getter
@Component
@ConfigurationProperties(prefix = "sys")
public class SysConfig {
private String tokenExcepPattern;
private String tokenkeyPrefex;
private String token;
private String clientid;
private String clientsecret;
}
@Getter
@Setter
public class JWT {
private String access_token;
private String token_type;
private String refresh_token;
private int expires_in;
private String scope;
private String jti;
}
等我研究完cloud ali系列在放源码吧
-end-