摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-auth-1/ 「芋道源码」欢迎转载,保留摘要,谢谢!

1. 概述

本文分享 Portal 的认证与授权,侧重在认证部分。

Apollo 是配置管理系统,会提供权限管理(Authorization),理论上是不负责用户登录认证功能的实现(Authentication)。

所以 Apollo 定义了一些SPI用来解耦,Apollo 接入登录的关键就是实现这些 SPI 。

和我们理解的 JDK SPI 不同,Apollo 是基于 Spring Profile 的特性,配合上 Spring Java Configuration 实现了类似 SPI 的功能。对于大多数人,我们可能比较熟悉的是,基于不同的 Profile 加载不同环境的 yaml 或 properties 配置文件。所以,当笔者看到这样的玩法,也是眼前一亮。

在 apollo-portal 项目中,spi 包下,我们可以看到认证相关的配置与实现,如下图所示:

绿框:接口。

紫框:实现。

红框:配置接口对应的实现。

2. AuthConfiguration

com.ctrip.framework.apollo.portal.spi.configuration.AuthConfiguration ,认证 Spring Java 配置。如下图:

目前有三种实现:

第一种, profile=ctrip ,携程内部实现,接入了SSO并实现用户搜索、查询接口。

第二种,profile=auth ,使用 Apollo 提供的 Spring Security 简单认证。

第三种,profile 为空,使用默认实现,全局只有 apollo 一个账号。

一般情况下,我们使用第二种,基于 Spring Security 的实现。所以本文仅分享这种方式。对其他方式感兴趣的胖友,可以自己读下代码哈。

整体类图如下:

2.1 SpringSecurityAuthAutoConfiguration

UserService ,配置如下:

@Bean

@ConditionalOnMissingBean(UserService.class)

public UserService springSecurityUserService() {

return new SpringSecurityUserService();

}

使用 SpringSecurityUserService 实现类,在 「5. UserService」 中,详细解析。

UserInfoHolder ,配置如下:

@Bean

@ConditionalOnMissingBean(UserInfoHolder.class)

public UserInfoHolder springSecurityUserInfoHolder() {

return new SpringSecurityUserInfoHolder();

}

使用 SpringSecurityUserInfoHolder 实现类,在 「6. UserInfoHolder」 中,详细解析。

JdbcUserDetailsManager ,配置如下:

@Bean

public JdbcUserDetailsManager jdbcUserDetailsManager(AuthenticationManagerBuilder auth, DataSource datasource) throws Exception {

JdbcUserDetailsManager jdbcUserDetailsManager = auth.jdbcAuthentication() // 基于 JDBC

.passwordEncoder(new BCryptPasswordEncoder()) // 加密方式为 BCryptPasswordEncoder

.dataSource(datasource) // 数据源

.usersByUsernameQuery("select Username,Password,Enabled from `Users` where Username = ?") // 使用 Username 查询 User

.authoritiesByUsernameQuery("select Username,Authority from `Authorities` where Username = ?") // 使用 Username 查询 Authorities

.getUserDetailsService();

jdbcUserDetailsManager.setUserExistsSql("select Username from `Users` where Username = ?"); // 判断 User 是否存在

jdbcUserDetailsManager.setCreateUserSql("insert into `Users` (Username, Password, Enabled) values (?,?,?)"); // 插入 User

jdbcUserDetailsManager.setUpdateUserSql("update `Users` set Password = ?, Enabled = ? where Username = ?"); // 更新 User

jdbcUserDetailsManager.setDeleteUserSql("delete from `Users` where Username = ?"); // 删除 User

jdbcUserDetailsManager.setCreateAuthoritySql("insert into `Authorities` (Username, Authority) values (?,?)"); // 插入 Authorities

jdbcUserDetailsManager.setDeleteUserAuthoritiesSql("delete from `Authorities` where Username = ?"); // 删除 Authorities

jdbcUserDetailsManager.setChangePasswordSql("update `Users` set Password = ? where Username = ?"); // 更新 Authorities

return jdbcUserDetailsManager;

}

org.springframework.security.provisioning.JdbcUserDetailsManager ,继承 JdbcDaoImpl 的功能,提供了一些很有用的与 Users 和 Authorities 表相关的方法。

SsoHeartbeatHandler ,配置如下:

@Bean

@ConditionalOnMissingBean(SsoHeartbeatHandler.class)

public SsoHeartbeatHandler defaultSsoHeartbeatHandler() {

return new DefaultSsoHeartbeatHandler();

}

使用 DefaultSsoHeartbeatHandler 实现类,在 「7. SsoHeartbeatHandler」 中,详细解析。

LogoutHandler ,配置如下:

@Bean

@ConditionalOnMissingBean(LogoutHandler.class)

public LogoutHandler logoutHandler() {

return new DefaultLogoutHandler();

}

使用 DefaultLogoutHandler 实现类,在 「8. LogoutHandler」 中,详细解析。

2.2 SpringSecurityConfigureration

@Order(99)

@Profile("auth")

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

static class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter {

public static final String USER_ROLE = "user";

@Override

protected void configure(HttpSecurity http) throws Exception {

http.csrf().disable(); // 关闭打开的 csrf 保护

http.headers().frameOptions().sameOrigin(); // 仅允许相同 origin 访问

http.authorizeRequests()

.antMatchers("/openapi/**", "/vendor/**", "/styles/**", "/scripts/**", "/views/**", "/img/**").permitAll() // openapi 和 资源不校验权限

.antMatchers("/**").hasAnyRole(USER_ROLE); // 其他,需要登录 User

http.formLogin().loginPage("/signin").permitAll().failureUrl("/signin?#/error").and().httpBasic(); // 登录页

http.logout().invalidateHttpSession(true).clearAuthentication(true).logoutSuccessUrl("/signin?#/logout"); // 登出(退出)

http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/signin")); // 未身份校验,跳转到登录页

}

}

@EnableWebSecurity 注解,禁用 Boot 的默认 Security 配置,配合 @Configuration 启用自定义配置(需要继承 WebSecurityConfigurerAdapter )。

@EnableGlobalMethodSecurity(prePostEnabled = true) 注解,启用 Security 注解,例如最常用的 @PreAuthorize 。

注意,.antMatchers("/**").hasAnyRole(USER_ROLE); 代码块,设置统一的 URL 的权限校验,只判断是否为登陆用户。另外,#hasAnyRole(...) 方法,会自动添加 "ROLE_" 前缀,所以此处的传参是 "user" 。代码如下:

// ExpressionUrlAuthorizationConfigurer.java

private static String hasAnyRole(String... authorities) {

String anyAuthorities = StringUtils.arrayToDelimitedString(authorities,

"','ROLE_");

return "hasAnyRole('ROLE_" + anyAuthorities + "')";

}

3. Users

Users 表,对应实体 com.ctrip.framework.apollo.portal.entity.po.UserPO ,代码如下:

@Entity

@Table(name = "Users")

public class UserPO {

/**

* 编号

*/

@Id

@GeneratedValue

@Column(name = "Id")

private long id;

/**

* 账号

*/

@Column(name = "Username", nullable = false)

private String username;

/**

* 密码

*/

@Column(name = "Password", nullable = false)

private String password;

/**

* 邮箱

*/

@Column(name = "Email", nullable = false)

private String email;

/**

* 是否开启

*/

@Column(name = "Enabled", nullable = false)

private int enabled;

}

字段比较简单,胖友自己看注释。

3.1 UserInfo

com.ctrip.framework.apollo.portal.entity.bo.UserInfo ,User BO 。代码如下:

public class UserInfo {

/**

* 账号 {@link com.ctrip.framework.apollo.portal.entity.po.UserPO#username}

*/

private String userId;

/**

* 账号 {@link com.ctrip.framework.apollo.portal.entity.po.UserPO#username}

*/

private String name;

/**

* 邮箱 {@link com.ctrip.framework.apollo.portal.entity.po.UserPO#email}

*/

private String email;

}

在 UserPO 的 #toUserInfo() 方法中,将 UserPO 转换成 UserBO ,代码如下:

public UserInfo toUserInfo() {

UserInfo userInfo = new UserInfo();

userInfo.setName(this.getUsername());

userInfo.setUserId(this.getUsername());

userInfo.setEmail(this.getEmail());

return userInfo;

}

注意,userId 和 name 属性,都是指向 User.username 。

4. Authorities

Authorities 表,Spring Security 中的 Authority ,实际和 Role 角色等价。表结构如下:

`Id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id',

`Username` varchar(50) NOT NULL,

`Authority` varchar(50) NOT NULL,

目前 Portal 只有一种角色 "ROLE_user" 。如下图所示:

为什么是这样的呢?在 Apollo 中,

统一的 URL 的权限校验,只判断是否为登陆用户,在 SpringSecurityConfigureration 中,我们可以看到。

具体每个 URL 的权限校验,通过在对应的方法上,添加 @PreAuthorize 方法注解,配合具体的方法参数,一起校验功能 + 数据级的权限校验。

5. UserService

com.ctrip.framework.apollo.portal.spi.UserService ,User 服务接口,用来给 Portal 提供用户搜索相关功能。代码如下:

public interface UserService {

List searchUsers(String keyword, int offset, int limit);

UserInfo findByUserId(String userId);

List findByUserIds(List userIds);

}

5.1 SpringSecurityUserService

com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserService ,基于 Spring Security 的 UserService 实现类。

5.5.1 构造方法

private PasswordEncoder encoder = new BCryptPasswordEncoder();

/**

* 默认角色数组,详细见 {@link #init()}

*/

private List authorities;

@Autowired

private JdbcUserDetailsManager userDetailsManager;

@Autowired

private UserRepository userRepository;

@PostConstruct

public void init() {

authorities = new ArrayList<>();

authorities.add(new SimpleGrantedAuthority("ROLE_user"));

}

authorities 属性,只有一个元素,为 "ROLE_user" 。

5.5.2 createOrUpdate

#createOrUpdate(UserPO) 方法,创建或更新 User 。代码如下:

1: @Transactional

2: public void createOrUpdate(UserPO user) {

3: String username = user.getUsername();

4: // 创建 Spring Security User

5: User userDetails = new User(username, encoder.encode(user.getPassword()), authorities);

6: // 若存在,则进行更新

7: if (userDetailsManager.userExists(username)) {

8: userDetailsManager.updateUser(userDetails);

9: // 若不存在,则进行新增

10: } else {

11: userDetailsManager.createUser(userDetails);

12: }

13: // 更新邮箱

14: UserPO managedUser = userRepository.findByUsername(username);

15: managedUser.setEmail(user.getEmail());

16: userRepository.save(managedUser);

17: }

第 5 行:创建 com.ctrip.framework.apollo.portal.spi.springsecurity.User 对象。

使用 PasswordEncoder 对 password 加密。

传入对应的角色 authorities 参数。

第 6 至 12 行:新增或更新 User 。

第 13 至 16 行:更新 email 。不直接在【第 6 至 12 行】处理的原因是,com.ctrip.framework.apollo.portal.spi.springsecurity.User 中没有 email 属性。

5.5.3 其他实现方法

portal认证 java_Apollo 源码解析 —— Portal 认证与授权(一)之认证相关推荐

  1. .Net Core 认证组件源码解析

    不知不觉.Net Core已经推出到3.1了,大多数以.Net为技术栈的公司也开始逐步的切换到了Core,从业也快3年多了,一直坚持着.不管环境怎么变,坚持自己的当初的选择,坚持信仰 .Net Cor ...

  2. 微软OA认证/ADFS认证 java 源码解析

    依照项目的需要,客户需要使用微软的认证,仔细走了一遍官方的源码,希望可以给后来人 指条路 1.先去微软的官网下载java版本的源代码 下载地址 上面的下载页面 会有一套具体的申请账号+配置流程 ,跟着 ...

  3. .Net Core 认证系统之基于Identity Server4 Token的JwtToken认证源码解析

    介绍JwtToken认证之前,必须要掌握.Net Core认证系统的核心原理,如果你还不了解,请参考.Net Core 认证组件源码解析,且必须对jwt有基本的了解,如果不知道,请百度.最重要的是你还 ...

  4. rest_framework-00-规范-APIview源码解析-认证

    rest_framework-00-规范-APIview源码解析-认证 规范 支付宝: 接口开发 订单api----order 方式1:缺点:如果有10张表,则需要40个url. urls.py vi ...

  5. 【SpringSecurity系列02】SpringSecurity 表单认证逻辑源码解读

    概要 前面一节,通过简单配置即可实现SpringSecurity表单认证功能,而今天这一节将通过阅读源码的形式来学习SpringSecurity是如何实现这些功能, 前方高能预警,本篇分析源码篇幅较长 ...

  6. 计算机毕业设计Java创新学分认证系统(源码+系统+mysql数据库+lw文档)

    计算机毕业设计Java创新学分认证系统(源码+系统+mysql数据库+lw文档) 计算机毕业设计Java创新学分认证系统(源码+系统+mysql数据库+lw文档) 本源码技术栈: 项目架构:B/S架构 ...

  7. Ocelot简易教程(七)之配置文件数据库存储插件源码解析

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html 上篇文章给大家分享了如何集成我写的一个Ocelot扩展插件把Ocelot的配置存储 ...

  8. shiro反序列化工具_Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437)源码解析

    Apache Shiro Apache Shiro是一个功能强大且灵活的开源安全框架,主要功能包括用户认证.授权.会话管理以及加密.在了解该漏洞之前,建议学习下Apache Shiro是怎么使用. d ...

  9. ThinkPHP源码解析之控制器

    本文会对控制器的执行顺序还有实现过程.源码解析给出解析, ThinkPHP源码解析之控制器 前言 一.实例化控制器 二.关于ArrayAccess和直接执行魔术访问返回实例的区别 三.执行控制器中的方 ...

最新文章

  1. 图解Oracle 11g physical standby Rolling Upgrade物理备库滚动升级特性
  2. git add 失效
  3. 两数相加Python解法
  4. 会做饭的机器人曰记_颜真卿《麻姑仙坛记》:苍劲古朴,体态沉雄,气象宏大...
  5. oracle按用户采集信息,oracle 11g 手动收集用户统计信息
  6. bzoj 4006 管道连接 —— 斯坦纳树+状压DP
  7. HTML5网站大观:分享8个精美的 HTML5 网站案例
  8. 技能高考多少分上本科计算机,技能高考多少分上一本
  9. IEC61850的Read请求报文件MMS PDU解码
  10. IOS-UIButton的文本与图片的布局
  11. 比较好的文章地址收集
  12. 【字节跳动】数据分析师面经
  13. 用JavaScript写的简单本地视频播放器
  14. 《深入理解计算机系统》实验四Architecture Lab
  15. video标签的使用以及通过js控制视频开始与暂停
  16. 幸福和不幸福是对比出来的
  17. txt电子书如何用Mac打开?
  18. DB2数据库的备份与恢复
  19. 上学最恐怖的事在于上课前点名签到,尤其这个签到脚本更恐怖。
  20. Oracle(四)Oracle 函数

热门文章

  1. Python:实现double factorial recursive双阶乘递归算法(附完整源码)
  2. 35岁程序员真的要去卖炒饭了吗?
  3. python模拟app登陆_python scrapy版 模拟登陆appAnnie
  4. 记录一下自己牛客网刷题的错题
  5. 实战文本分类对抗攻击
  6. A Little Love
  7. 手把手教你看懂Chrome火焰图!(调试性能必备)
  8. 我和我的小伙伴们一起学Unity3D(三)GUISkin 界面皮肤
  9. stm32中用到的实时系统_RALM: 实时 look-alike 算法在推荐系统中的应用
  10. 《时空穿越者》--失败男人的幻觉