建立此项目的目的:系统主要是对外提供数据,发布数据,且对接系统较多,同时还要对发布的接口做监控,审计等管理。前期项目考虑的比较简单,使用技术有:springcloud,oauth2,spring security,zull,Eureka,redis等技术,单独做一个认证服务器,资源权限验证放在路由层,路由层使用zuul-ratelimit 进行api限流,zuul自己的断路处理(FallbackProvider)。前期大致是这些功能,后期会慢慢增加。
首先创建认证服务器
创建认证中心类CustomAuthorizationServerConfig
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
/**
* @Description: 配置授权认证服务类
* @author: wf
* @Date: 2018-11-22
* @Time: 13:41
*/
@Configuration
@EnableAuthorizationServer //授权认证中心
public class CustomAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 权限管理器
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 数据源,保存token的时候需要
*/
@Autowired
private DataSource dataSource;
/**
* 设置保存token的方式,一共有五种,这里采用数据库的方式
*/
@Autowired
private TokenStore tokenStore;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 自定义登录或者鉴权失败时的返回信息
*/
@Resource(name = "webResponseExceptionTranslator")
private WebResponseExceptionTranslator webResponseExceptionTranslator;
@Resource(name = "signUserDetailService")
private UserDetailsService userDetailsService;
/******************配置区域**********************/
/**
* 用来配置授权(authorizatio)以及令牌(token)的访问端点和令牌服务 核心配置 在启动时就会进行配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//开启密码授权类型
endpoints.authenticationManager(authenticationManager);
//配置token存储方式
endpoints.tokenStore(tokenStore);
//自定义登录或者鉴权失败时的返回信息
endpoints.exceptionTranslator(webResponseExceptionTranslator);
}
/**
* 用来配置令牌端点(Token Endpoint)的安全约束.
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/**
* 配置oauth2服务跨域
*/
CorsConfigurationSource source = new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedOrigin(request.getHeader(HttpHeaders.ORIGIN));
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(3600L);
return corsConfiguration;
}
};
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients()
.addTokenEndpointAuthenticationFilter(new CorsFilter(source));
}
/**
* 用来配置客户端详情服务(ClientDetailsService),
* 客户端详情信息在这里进行初始化, 数据库在进行client_id 与 client_secret验证时 会使用这个service进行验证
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
资源提供端(主要是通过资源服务器中进行token验证)
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;
/**
* @Description: 资源提供端的配置 目前和授权认证端处于同一应用
* @author: wf
* @Date: 2018-11-22
* @Time: 16:58
*/
@Configuration
@EnableResourceServer //开启资源提供服务的配置 是默认情况下spring security oauth2的http配置 会被WebSecurityConfigurerAdapter的配置覆盖
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//定义哪些url需要被保护 哪些不需要保护
.antMatchers("/oauth/token" , "oauth/check_token").permitAll()//定义这两个链接不需要登录可访问
/* .antMatchers("/**").permitAll() *///定义所有的都不需要登录 目前是测试需要
.anyRequest().authenticated() //其他的都需要登录
//.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色
.and()
.formLogin().loginPage("/user/view/login")//如果未登录则跳转登录的页面 这儿可以控制登录成功和登录失败跳转的页面
.loginProcessingUrl("/user/login")
.usernameParameter("username").passwordParameter("password").permitAll()//定义号码与密码的parameter
.and()
.csrf().disable();//防止跨站请求 spring security中默认开启
}
}
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import com.neusoft.auth.UserDetail;
import com.neusoft.domain.entity.Role;
import com.neusoft.domain.entity.User;
import com.neusoft.service.IUserService;
import com.neusoft.service.RoleService;
/**
* @Description:
* @author: wf
* @Date: 2018-11-22
* @Time: 15:07
*/
@Component("signUserDetailService")
public class SignUserDetaiServiceConfig implements UserDetailsService {
@Autowired
private IUserService userService;
@Autowired
private RoleService roleService;
/**
* 启动刷新token授权类型,会判断用户是否还是存活的
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User currentUser = userService.getByName(s);
if ( currentUser == null ) {
throw new UsernameNotFoundException("用户没用找到");
}
List<Role>roles= roleService.queryByUser(currentUser.getId());
List authorisList = new ArrayList();
for(Role r : roles){
authorisList.add(new SimpleGrantedAuthority(r.getAuth_role_id()));
}
UserDetails ud= new UserDetail(currentUser,authorisList);
return ud;
}
}
import com.neusoft.auth.CustomerAuthenticationProvider;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
/**
* @Description: //是默认情况下spring security的http配置 优于ResourceServerConfigurerAdapter的配置
* @author: wf
* @Date: 2018-11-22
* @Time: 17:06
*/
@Configuration //开启三种可以在方法上面加权限控制的注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityAdaptConfig extends WebSecurityConfigurerAdapter {
/**
* 获取用户的验证配置类
*/
@Resource(name = "signUserDetailService")
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Resource(name = "myCustomerAuthenticationProvider")
private CustomerAuthenticationProvider customerAuthenticationProvider;
/**
* 配置资源服务端的http设置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//定义哪些url需要被保护 哪些不需要保护
.antMatchers("/oauth/token" , "oauth/check_token","actuator/*").permitAll()//定义这两个链接不需要登录可访问
/* .antMatchers("/**").permitAll() *///定义所有的都不需要登录 目前是测试需要
.anyRequest().authenticated() //其他的都需要登录
//.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色
.and()
.formLogin().loginPage("/login")//如果未登录则跳转登录的页面 这儿可以控制登录成功和登录失败跳转的页面
.usernameParameter("username").passwordParameter("password").permitAll()//定义号码与密码的parameter
.and()
.csrf().disable();//防止跨站请求 spring security中默认开启
}
/**
* 权限管理器 AuthorizationServerConfigurerAdapter认证中心需要的AuthenticationManager需要
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//目的是为了前端获取数据时获取到整个form-data的数据,提供验证器
auth.authenticationProvider(customerAuthenticationProvider);
//配置登录user验证处理器 以及密码加密器 好让认证中心进行验证
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
/**
* 需要配置这个支持password模式
* support password grant type
*
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
/**
* @Description: AuthenticationManagerBuilder中的AuthenticationProvider是进行认证的核心
* @author: wf
* @Date: 2018-11-23
* @Time: 9:11
*/
@Component("myCustomerAuthenticationProvider")
public class CustomerAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@Resource(name = "signUserDetailService")
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 手动实现认证
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails , UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if ( authentication.getCredentials() == null ) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials" , "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if ( !this.passwordEncoder.matches(presentedPassword , userDetails.getPassword()) ) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials" , "Bad credentials"));
}
}
}
/**
* 手动加载user
*/
@Override
protected UserDetails retrieveUser(String s , UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
return userDetailsService.loadUserByUsername(s);
}
// public static final Logger LOGGER = LoggerFactory.getLogger(CustomerAuthenticationProvider.class);
//
//
//
// /**
// *authentication是前台拿过来的号码密码bean
// */
// @Override
// public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// LOGGER.info("用户输入的用户名是:" + authentication.getName());
// LOGGER.info("用户输入的密码是:" + authentication.getCredentials());
//
//
// // 根据用户输入的用户名获取该用户名已经在服务器上存在的用户详情,如果没有则返回null
// UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getName());
// try {
//
// LOGGER.info("服务器上已经保存的用户名是:" + userDetails.getUsername());
// LOGGER.info("服务器上保存的该用户名对应的密码是: " + userDetails.getPassword());
// LOGGER.info("服务器上保存的该用户对应的权限是:" + userDetails.getAuthorities());
//
// if(authentication.getCredentials().equals(userDetails.getPassword())){
// //验证成功 将返回一个UsernamePasswordAuthenticaionToken对象
// LOGGER.info("LOGIN SUCCESS !!!!!!!!!!!!!!!!!!!");
// //分别返回用户实体 输入的密码 以及用户的权限
// return new UsernamePasswordAuthenticationToken(userDetails,authentication.getCredentials(),userDetails.getAuthorities());
// }
// } catch (Exception e){
// LOGGER.error("author failed, -------------------the error message is:-------- " + e);
// throw e;
// }
// //如果验证不同过则返回null或者抛出异常
// return null;
// }
//
// /**
// *
// **/
// @Override
// public boolean supports(Class<?> aClass) {
// return true;
// }
}
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.neusoft.domain.entity.User;
/**
* @Description: 用户信息的实体类
* @author: wf
* @Date: 2018-11-22
* @Time: 15:46
*/
public class UserDetail implements UserDetails {
@Autowired
private User user;
private String id;
private Collection<? extends GrantedAuthority> roles;
/**
* 通过构造方法在UserDetailsService的方法中将查到的user注入进去
*/
public UserDetail(User user, Collection<? extends GrantedAuthority> col) {
this.user = user;
if ( user != null ) {
this.id = user.getId();
}
if(col!=null){
this.roles=col;
}
}
/**
* 对当前的用户赋予其应有的权限
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//添加权限
return this.roles;
}
/**
* 获取密码
*/
@Override
public String getPassword() {
return user.getPassword();
}
/**
* 获取用户名
*/
@Override
public String getUsername() {
return user.getUsername();
}
/**
* 账户是否未过期
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 账户是否未锁定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 证书是否未过期
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否有效 可对应数据库中的delete_flag字段
*/
@Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.neusoft</groupId>
<artifactId>auth_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>auth_server</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>com.neusoft</groupId>
<artifactId>center</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<pagehelper.version>1.1.0</pagehelper.version>
<mybatis.generator.version>1.3.2</mybatis.generator.version>
<fastjson.version>1.2.31</fastjson.version>
<jackson.version>2.9.7</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>com.neusoft</groupId>
<artifactId>center_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--spring-security-oauth2 begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--spring-security-oauth2 end-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml配置
spring:
profiles:
#包含的配置项
include:
- single-datasource
# - quartz
#启用配置项级别
active: prod
#色彩日志输出
output:
ansi:
enabled: always
application:
name: auth_server
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8080/eureka/
mybatis:
mapper-locations: classpath*:**/mappers/*.xml
type-aliases-package: com.neusoft.domain.entity
configuration:
map-underscore-to-camel-case: true
use-generated-keys: true
use-column-label: true
cache-enabled: true
call-setters-on-nulls: true
logging:
level:
root: info
org:
springframework: info
server:
port: 8081
以上是认证服务器的主要代码,权限是用的动态编码的,从数据库中查找