在前面的文章中,我们自定义了一些CA登录相关的类,如 CertificateAuthorityAuthenticationToken、CertificateAuthorityAuthenticationFilter、CertificateAuthorityDaoAuthenticationProvider等。但是,由于诸多条件的不具备,也就没有办法演示。
不过,目前一个新登录方式所需要的逻辑基本都介绍过了,下面,就利用这些已经自定义的类,来尝试如何自定义个新的登录方式-CA登录。并且,CA登录需要与默认的用户名密码登录方式共存,也即用户可以自由选择何种登录方式来登录系统。
登录页面改造
首先,我们来改造一下登录页面,添加CA登录方式。
<div class="login-form">
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item">
<a class="nav-link active" href="#login" data-toggle="tab">用户登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#ca_login" data-toggle="tab">CA登录</a>
</li>
</ul>
<div class="tab-content">
<div id="login" class="tab-pane active">
<form th:action="@{/login}" method="post" th:method="post" class="mt-1">
<div class="form-group">
<input type="text" class="form-control" name="username" placeholder="用户名">
</div>
<div class="form-group">
<input type="password" class="form-control" name="password" placeholder="密码">
</div>
<div class="checkbox">
<label><input type="checkbox"> 记住我</label>
</div>
<button type="submit" class="btn btn-primary btn-block mb-1 mt-1">登录</button>
<p class="text-muted text-center"> <a href="login.html#">
<small>忘记密码了?</small></a> | <a href="#">注册一个新账号</a>
</p>
</form>
</div>
<div id="ca_login" class="tab-pane">
<form th:action="@{/certificate_authority_login}" method="post" th:method="post" class="mt-1">
<div class="form-group">
<input type="text" class="form-control" name="signature" value="3691308F2A4C2F6983F2880D32E29C84" readonly />
</div>
<button type="submit" class="btn btn-primary btn-block mb-1 mt-1">登录</button>
<p class="text-muted text-center"> <a href="login.html#">
<small>忘记密码了?</small></a> | <a href="#">注册一个新账号</a>
</p>
</form>
</div>
</div>
</div>
页面效果如下:
使用CA签名获取用户信息
首先,需要修改 SYS_USER 表结构,新增 signature 字段,即CA签名,当然,也可以是能标识CA唯一性的其它属性。
alter table SYS_USER add signature varchar(60) comment 'ca签名';
接下来,把之前用户名为 zhangsan 的数据,添加 signature 字段值。
UPDATE sys_user SET signature = '3691308F2A4C2F6983F2880D32E29C84' WHERE ID = '2031a4adc78942d59188cea7927e6304';
然后,UserDao 新增使用 signature 查询用户信息的方法。
public SysUser getBySignature(String signature) {
String sql = "select id, name, username, password, age, state from sys_user where signature = :signature";
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("signature", signature);
return get(sql, paramMap, SysUser.class);
}
最后,自定义 UserDetailsService 接口实现 CertificateAuthorityJdbcUserDetailsService,以注入 UserDao 查询用户信息。
public UserDetails loadUserByUsername(String signature) throws UsernameNotFoundException {
SysUser sysUser = this.userDao.getBySignature(signature);
User.UserBuilder builder = User.builder()
.username(sysUser.getUsername())
.password(sysUser.getPassword());
List<String> roles = this.roleDao.listRoleCodeByUserId(sysUser.getId());
builder.roles(roles == null ? new String[] {} : roles.toArray(new String[] {}));
return builder.build();
}
Spring Security配置改造
最后,把CA登录相关的配置添加到 Spring Security 配置中即可。
@EnableWebSecurity
@Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
......
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(certificateAuthorityDaoAuthenticationProvider())
.userDetailsService(customJdbcUserDetailsService())
.passwordEncoder(new BCryptPasswordEncoder());
}
private AbstractAuthenticationProcessingFilter certificateAuthorityAuthenticationFilter() throws Exception {
CertificateAuthorityAuthenticationFilter authorityAuthenticationFilter = new CertificateAuthorityAuthenticationFilter();
authorityAuthenticationFilter.setAuthenticationSuccessHandler(certificateAuthorityAuthenticationSuccessHandler());
authorityAuthenticationFilter.setAuthenticationFailureHandler(certificateAuthorityAuthenticationFailureHandler());
authorityAuthenticationFilter.setAuthenticationManager(authenticationManager());
return authorityAuthenticationFilter;
}
private AuthenticationProvider certificateAuthorityDaoAuthenticationProvider() {
CertificateAuthorityDaoAuthenticationProvider certificateAuthorityDaoAuthenticationProvider = new CertificateAuthorityDaoAuthenticationProvider();
certificateAuthorityDaoAuthenticationProvider.setUserDetailsService(certificateAuthorityJdbcUserDetailsService());
return certificateAuthorityDaoAuthenticationProvider;
}
private UserDetailsService certificateAuthorityJdbcUserDetailsService() {
CertificateAuthorityJdbcUserDetailsService userDetailsService = new CertificateAuthorityJdbcUserDetailsService();
userDetailsService.setUserDao(userDao);
userDetailsService.setRoleDao(roleDao);
return userDetailsService;
}
private AuthenticationSuccessHandler certificateAuthorityAuthenticationSuccessHandler() {
SavedRequestAwareAuthenticationSuccessHandler authenticationSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
authenticationSuccessHandler.setDefaultTargetUrl("/index");
return authenticationSuccessHandler;
}
private AuthenticationFailureHandler certificateAuthorityAuthenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler authenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler();
authenticationFailureHandler.setDefaultFailureUrl("/login_fail");
return authenticationFailureHandler;
}
......
}
登录尝试
首先,我们尝试CA登录方式。点击登录页面的CA登录TAB页,切换到CA登录方式,此时,CA的相关信息可正常显示到页面中(由CA厂商提供的API从插入电脑USB口的CA Key中获取)。
然后,点击 登录 按钮(注意,真实情况下,需要输入CA Key的密码,此处省略)系统正常登录。
查看个人中心,用户正确无误。
接下来,退出登录,切换到用户登录TAB页,使用用户名密码方式再次登录,同样可以正常登录系统,并无影响。两种登录方式完美共存。
CA登录方式实现及配置完成。