上节架构了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 全文
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.1.RELEASE
com.linbin
Eureda-Client-zuul
0.0.1-SNAPSHOT
Eureda-Client-zuul
Demo project for Spring Boot
UTF-8
UTF-8
1.8
Greenwich.M3
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-config
org.springframework.boot
spring-boot-starter-security
org.springframework.security
spring-security-cas
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
org.springframework.boot
spring-boot-maven-plugin
关键依赖:
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 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 authorities = new HashSet();
@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 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() +"
登陆权限:";
for (GrantedAuthority grantedAuthority : userDetails.getAuthorities()) {
user += ("
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认证系统的流程
参考了多位网友的文章,在这一致致谢!