一、 概述
本文使用Springsecurity、Oauth2 + JWT实现单点登录功能。
承接上一篇文章:《第一篇》
本文为进阶篇,更细致的实现了 Springsecurity安全框架的 各部分handler处理,让系统运行起来更加细致,灵活。
二、架构参考
1. 使用架构
- springboot 2.3.1
- springSecurity
- oauth2 jwt
- mybatis plus
- ehcache
- swagger
- druid
- thymelef + layui
2. SSO 时序图
三、代码参考
1. Server端:
主要实现 “授权服务器、资源服务器、自定义登录校验、生成token” 等,后续集成RBAC权限管理,
闲话不多说,上代码:
- pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<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.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- Web拦截器 WebSecurityConfigurerAdapter
- 实现访问拦截、SpringSecurity自定义handler定义、记住密码等功能:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsServiceImpl userDetailsService;
/**
* 登录成功逻辑
*/
@Resource
private MyAuthenticationSuccessHandler mySuccessHandler;
/**
* 登录失败逻辑
*/
@Resource
private MyAuthenticationFailureHandler myFailureHandler;
@Resource
private MyLogoutSuccessHandler myLogoutHandler;
/**
* 无权访问 JSON 格式的数据
*/
@Resource
private RestfulAccessDeniedHandler accessDeniedHandler;
@Autowired
@Qualifier("resourceServerRequestMatcher")
private RequestMatcher resources;
@Override
protected void configure(HttpSecurity http) throws Exception {
RequestMatcher nonResoures = new NegatedRequestMatcher(resources);
http.requestMatcher(nonResoures).authorizeRequests()
// http.authorizeRequests()
.antMatchers("/swagger-resources/**", "/PearAdmin/**", "/component/**",
"/admin/**", "/**/*.html", "/**/*.css", "/**/*.js", "/swagger-ui.html",
"/webjars/**", "/v2/**", "/druid/**", "/captcha").permitAll()
.anyRequest().authenticated() // 其他地址的访问均需验证权限
.and()
.formLogin()
.loginPage("/login")
//拦截的请求
// .loginProcessingUrl("/login")
// 登录成功
.successHandler(mySuccessHandler)
// 登录失败
.failureHandler(myFailureHandler)
.permitAll()
.and()
.rememberMe()
.rememberMeParameter("rememberme")
.tokenValiditySeconds(2 * 24 * 60 * 60)
.and()
.logout()
.logoutSuccessHandler(myLogoutHandler)
.and().cors()
.and()
.csrf().disable() // 防止iframe 造成跨域
.headers()
.frameOptions()
.disable();
// 禁用缓存
http.headers().cacheControl();
// 无权访问 JSON 格式的数据
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
- AuthenticationSuccessHandler
- 登录成功应答信息:
@Component("MyAuthenticationSuccessHandler")
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
//输出结果
Result result = Result.ok().message("登录成功");
response.getWriter().write(JSON.toJSONString(result));
}
}
- AuthenticationFailureHandler
- 登录失败应答信息:
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//修改编码格式
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType("application/json");
if (e instanceof BadCredentialsException){
httpServletResponse.getWriter().write(JSON.toJSONString(Result.error().message("用户名或密码错误")));
}else {
httpServletResponse.getWriter().write(JSON.toJSONString(Result.error().message(e.getMessage())));
}
}
}
- LogoutSuccessHandler
- 登出应答信息,如果client无状态处理,采用缓存存储用户信息,此处需要清除缓存.
- 登出时 返回请求来源页.
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getHeader("referer"));
}
}
- AccessDeniedHandler
- 无权限时、应答信息:
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println(JSONUtils.toJSONString(Result.error().message(e.getMessage())));
response.getWriter().flush();
}
}
- UserDetailsService 自定义登录校验
- 账号、密码校验、用户权限获取
@Service @Slf4j public class UserDetailsServiceImpl implements UserDetailsService { @Resource private UserService userService; @Resource private RoleUserService roleUserService; @Resource private RoleService roleService; @Resource private MenuDao menuDao; @Override public JwtUserDto loadUserByUsername(String userName) { //根据用户名获取用户 MyUser user = userService.getUserByName(userName); if (user == null) { throw new BadCredentialsException("用户名或密码错误"); } else if (user.getStatus().equals(MyUser.Status.LOCKED)) { throw new LockedException("用户被锁定,请联系管理员解锁"); } List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); List<MenuIndexDto> list = menuDao.listByUserId(user.getUserId()); List<String> collect = list.stream().map(MenuIndexDto::getPermission).collect(Collectors.toList()); for (String authority : collect) { if (!("").equals(authority) & authority != null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority); grantedAuthorities.add(grantedAuthority); } } //将用户所拥有的权限加入GrantedAuthority集合中 JwtUserDto loginUser = new JwtUserDto(user, grantedAuthorities); return loginUser; } }
本文着重介绍了 web拦截器及springSecurity各handler处理 相关代码,
后续文章会介绍授权服务器 与 资源服务器等配置,请移步《第三篇》。
喜欢的朋友请 “点赞收藏”,多谢支持!