本文使用Springsecurity、Oauth2 + JWT实现单点登录功能。
承接上一篇文章:《第一篇》
本文为进阶篇,更细致的实现了 Springsecurity安全框架的 各部分handler处理,让系统运行起来更加细致,灵活。
1. 使用架构
2. SSO 时序图
1. Server端:
主要实现 “授权服务器、资源服务器、自定义登录校验、生成token” 等,后续集成RBAC权限管理,
闲话不多说,上代码:
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-security
org.springframework.security.oauth.boot
spring-security-oauth2-autoconfigure
2.1.3.RELEASE
org.springframework.boot
spring-boot-starter-thymeleaf
@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());
}
}
@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));
}
}
@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())));
}
}
}
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getHeader("referer"));
}
}
@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();
}
}
@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 grantedAuthorities = new ArrayList<>();
List list = menuDao.listByUserId(user.getUserId());
List 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处理 相关代码,
后续文章会介绍授权服务器 与 资源服务器等配置,请移步《第三篇》。
喜欢的朋友请 “点赞收藏”,多谢支持!