【SpringSecurity OAuth2 JWT】实现SSO单点登录(二)

一、 概述

本文使用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处理 相关代码,

后续文章会介绍授权服务器 与 资源服务器等配置,请移步《第三篇》


喜欢的朋友请 “点赞收藏”,多谢支持!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值