上节架构了spring zuul实现微服务的网页路由,因为zuul是微服务群的统一入口,非常适合在zuul服务上进行统一登录认证,本节实验结合spring zuul +spring security +Apereo cas实现微服务群的统一登录认证
spring security是一个spring的权限认证系统,cas是单位中央认证系统,从中央认证系统认证后,获取一个中央认证系统的身份(本测试中认证用户名linbin),spring security对这个用户名进行映射,比如这里映射为spring security的登录用户admin(可以通过数据库查询映射关系),并设置admin用户的权限,从而实现统一登录认证到本spring boot微服务的本地系统用户和权限的转换,而因zuul的统一api入口地位,这个登录可以作为整个微服务群的统一登录
1. 新建spring boot 微服务项目 Eureka-Client-zuul
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>
-
<parent>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-parent
</artifactId>
-
<version>2.1.1.RELEASE
</version>
-
<relativePath/>
<!-- lookup parent from repository -->
-
</parent>
-
<groupId>com.linbin
</groupId>
-
<artifactId>Eureda-Client-zuul
</artifactId>
-
<version>0.0.1-SNAPSHOT
</version>
-
<name>Eureda-Client-zuul
</name>
-
<description>Demo project for Spring Boot
</description>
-
<properties>
-
<project.build.sourceEncoding>UTF-8
</project.build.sourceEncoding>
-
<project.reporting.outputEncoding>UTF-8
</project.reporting.outputEncoding>
-
<java.version>1.8
</java.version>
-
<spring-cloud.version>Greenwich.M3
</spring-cloud.version>
<!-- Finchley.M8 -->
-
</properties>
-
<dependencies>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-web
</artifactId>
-
</dependency>
-
<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-netflix-zuul
</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.cloud
</groupId>
-
<artifactId>spring-cloud-starter-config
</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-security
</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.security
</groupId>
-
<artifactId>spring-security-cas
</artifactId>
-
</dependency>
-
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-test
</artifactId>
-
<scope>test
</scope>
-
</dependency>
-
</dependencies>
-
<dependencyManagement>
-
<dependencies>
-
<dependency>
-
<groupId>org.springframework.cloud
</groupId>
-
<artifactId>spring-cloud-dependencies
</artifactId>
-
<version>${spring-cloud.version}
</version>
-
<type>pom
</type>
-
<scope>import
</scope>
-
</dependency>
-
</dependencies>
-
</dependencyManagement>
-
<repositories>
-
<repository>
-
<id>spring-milestones
</id>
-
<name>Spring Milestones
</name>
-
<url>https://repo.spring.io/milestone
</url>
-
<snapshots>
-
<enabled>false
</enabled>
-
</snapshots>
-
</repository>
-
</repositories>
-
<build>
-
<plugins>
-
<plugin>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-maven-plugin
</artifactId>
-
</plugin>
-
</plugins>
-
</build>
-
</project>
关键依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
</dependency>
2. application.properties 配置
server.port=8000
eureka.client.serviceUrl.defaultZone=http://admin:123@centos7:8888/eureka/
spring.application.name=Euredaclientzuul
zuul.routes.euredaclient1.path=/c1/**
zuul.routes.euredaclient1.serviceId=Euredaclient1
zuul.routes.springdemo.path=/sd/**
zuul.routes.springdemo.serviceId=SpringDemo
spring.session.store-type=none
3. java文件目录
4. 启动类EuredaClientZuulApplication
@EnableZuulProxy
@SpringBootApplication
public class EuredaClientZuulApplication {
public static void main(String[] args) {
SpringApplication.run(EuredaClientZuulApplication.class, args);
}
}
5.WebSecurityConfig 类 是 spring security 配置实例
-
@Configuration
-
@EnableWebSecurity
-
@EnableGlobalMethodSecurity(prePostEnabled =
true, jsr250Enabled =
true)
-
public
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
-
@Autowired
-
private AuthenticationEntryPoint authenticationEntryPoint;
-
@Autowired
-
private AuthenticationProvider authenticationProvider;
-
@Autowired
-
private SingleSignOutFilter singleSignOutFilter;
-
@Autowired
-
private LogoutFilter logoutFilter;
-
@Override
-
protected void configure(HttpSecurity http) throws Exception {
-
//所有都需要认证才能访问 //由于设置了验证filter访问为,/login/cas,所以必须通过验证,否则出现死循环
-
http
-
.authorizeRequests()
-
.antMatchers(
"/login/cas")
-
.permitAll()
-
.and()
-
.authorizeRequests()
-
.anyRequest()
-
.authenticated()
-
.and()
-
.httpBasic()
-
.authenticationEntryPoint(authenticationEntryPoint)
-
.and()
-
.logout()
-
.logoutSuccessUrl(
"/logout")
-
.and()
-
.addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
-
.addFilterBefore(logoutFilter, LogoutFilter.class);
-
}
-
@Override
-
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
-
auth.authenticationProvider(authenticationProvider);
-
}
-
@Override
-
protected AuthenticationManager authenticationManager() throws Exception {
-
//设置cas认证提供
-
return
new ProviderManager( Arrays.asList(authenticationProvider));
-
}
-
@Bean
-
public CasAuthenticationFilter casAuthenticationFilter(ServiceProperties sp) throws Exception {
-
//cas认证过滤器,当触发本filter时,对ticket进行认证
-
CasAuthenticationFilter filter =
new CasAuthenticationFilter();
-
filter.setServiceProperties(sp);
-
filter.setAuthenticationManager(authenticationManager());
return filter; }
-
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
-
}
-
}
6. CasSecurityConfig 实现与cas的接口配置
-
@Configuration
-
public
class CasSecurityConfig {
-
//cas服务
-
@Value(
"${cas.server.url:https://author.linbsoft.com/cas}")
-
private String casServerUrl;
-
@Bean
-
public ServiceProperties serviceProperties() {
-
ServiceProperties serviceProperties =
new ServiceProperties();
-
//本机服务,访问/login/cas时进行校验登录
-
serviceProperties.setService(
"http://springcloud.linbsoft.com:8000/login/cas");
-
serviceProperties.setSendRenew(
false);
-
return serviceProperties;
-
}
-
-
@Bean
-
@Primary
-
public AuthenticationEntryPoint authenticationEntryPoint( ServiceProperties sP) {
-
CasAuthenticationEntryPoint entryPoint =
new CasAuthenticationEntryPoint();
-
//cas登录服务
-
entryPoint.setLoginUrl(casServerUrl +
"/login");
-
entryPoint.setServiceProperties(sP);
-
return entryPoint;
-
}
-
@Bean
-
public TicketValidator ticketValidator() {
-
//指定cas校验器
-
return
new Cas30ServiceTicketValidator( casServerUrl);
-
}
-
//cas认证
-
@Bean
-
public CasAuthenticationProvider casAuthenticationProvider() {
-
CasAuthenticationProvider provider =
new CasAuthenticationProvider();
-
provider.setServiceProperties(serviceProperties());
-
provider.setTicketValidator(ticketValidator());
-
provider.setUserDetailsService(customUserDetailsService());
-
provider.setKey(
"CAS_PROVIDER_LOCALHOST_8000");
-
return provider;
-
}
-
@Bean
-
public UserDetailsService customUserDetailsService(){
-
return
new CustomUserDetailsService();
-
}
-
@Bean
-
public SecurityContextLogoutHandler securityContextLogoutHandler() {
-
return
new SecurityContextLogoutHandler();
-
}
-
@Bean
-
public LogoutFilter logoutFilter() {
-
//退出后转发路径
-
LogoutFilter logoutFilter =
new LogoutFilter( casServerUrl +
"/logout", securityContextLogoutHandler());
-
//cas退出
-
logoutFilter.setFilterProcessesUrl(
"/logout/cas");
-
return logoutFilter;
-
}
-
@Bean
-
public SingleSignOutFilter singleSignOutFilter() {
-
//单点退出
-
SingleSignOutFilter singleSignOutFilter =
new SingleSignOutFilter();
-
singleSignOutFilter.setCasServerUrlPrefix(casServerUrl);
-
singleSignOutFilter.setIgnoreInitConfiguration(
true);
-
return singleSignOutFilter;
-
}
-
//设置退出监听
-
@EventListener
-
public SingleSignOutHttpSessionListener singleSignOutHttpSessionListener( HttpSessionEvent event) {
-
return
new SingleSignOutHttpSessionListener();
-
}
-
-
}
7. CustomUserDetailsService 实现UserDetailsService接口
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("当前的用户名是:"+username);
//这里为了方便测试,就直接返回一个用户信息,实际当中这里修改为查询数据库或者调用服务什么的来获取用户信息
UserInfo userInfo = new UserInfo();
userInfo.setId(104566569938L);
userInfo.setUsername("admin");
userInfo.setName("admin");
Set<AuthorityInfo> authorities = new HashSet<AuthorityInfo>();
AuthorityInfo authorityInfo = new AuthorityInfo("TEST-Roll");
authorities.add(authorityInfo);
authorityInfo = new AuthorityInfo("write-table-roll");
authorities.add(authorityInfo);
userInfo.setAuthorities(authorities);
return userInfo;
}
}
8. UserInfo 保存登录用户信息的类,CustomUserDetailsService 需要调用
-
public
class UserInfo implements UserDetails {
-
private
static
final
long serialVersionUID = -
1041327031937199938L;
-
private Long id;
-
private String name;
-
private String username;
-
private String password;
-
private
boolean isAccountNonExpired =
true;
-
private
boolean isAccountNonLocked =
true;
-
private
boolean isCredentialsNonExpired =
true;
-
private
boolean isEnabled =
true;
-
private Set<AuthorityInfo> authorities =
new HashSet<AuthorityInfo>();
-
@Override
-
public Collection<? extends GrantedAuthority> getAuthorities() {
-
return authorities;
-
}
-
-
@Override
-
public String getPassword() {
-
return password;
-
}
-
-
@Override
-
public String getUsername() {
-
return username;
-
}
-
-
@Override
-
public boolean isAccountNonExpired() {
-
return
this.isAccountNonExpired;
-
}
-
-
@Override
-
public boolean isAccountNonLocked() {
-
return
this.isAccountNonLocked;
-
}
-
-
@Override
-
public boolean isCredentialsNonExpired() {
-
return
this.isCredentialsNonExpired;
-
}
-
-
@Override
-
public boolean isEnabled() {
-
return
this.isEnabled;
-
}
-
-
public void setUsername(String string) {
-
this.username=string;
-
}
-
-
public void setName(String string) {
-
this.name=string;
-
-
}
-
public String getName() {
-
return
this.name;
-
-
}
-
-
public void setAuthorities(Set<AuthorityInfo> authorities2) {
-
authorities=authorities2;
-
}
-
-
public Long getId() {
-
return id;
-
}
-
-
public void setId(Long id) {
-
this.id = id;
-
}
-
-
public void setAccountNonExpired(boolean isAccountNonExpired) {
-
this.isAccountNonExpired = isAccountNonExpired;
-
}
9. AuthorityInfo 是保存权限信息的类,UserInfo类需要使用
public class AuthorityInfo implements GrantedAuthority{
private static final long serialVersionUID = -175781100474818800L;
/**
* 权限CODE
*/
private String authority;
public AuthorityInfo(String authority) {
this.authority = authority;
}
@Override
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
}
10. 创建一个showuser类用来测试显示登录认证后的本地用户及权限
@RestController
public class showuser {
@RequestMapping("/user")
public String showloginuser() {
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
String user= "登陆用户:"+ userDetails.getUsername() +"<br>登陆权限:";
for (GrantedAuthority grantedAuthority : userDetails.getAuthorities()) {
user += ("<br>Authority:" + grantedAuthority.getAuthority());
}
return user;
}
}
11. 全部类及配置介绍完毕,启动Eureda server微服务,再启动本例 Eureka-Client-zuul 服务
浏览器 http://springcloud.linbsoft.com:8000 本案例地址
会自动条转到 cas登录界面,登录后在eclipse控制台可以看见cas登录用户
控制台显示的是cas中央认证系统的登录用户
12. 在浏览器跳转到本地再进入 http://springcloud.linbsoft.com:8000/user 可以看见映射的本地账号
到此,实现了登录外部中央认证系统到登录本地应用的spring secutiry认证系统的流程
参考了多位网友的文章,在这一致致谢!