spring security + spring session + spring oauth2 笔记

系列资料

最新版 Spring Security5.x 教程#博客
最新版 Spring Security5.x 教程#源码

笔记

spring security 流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGyD8QbP-1621588647483)(https://note.youdao.com/yws/api/personal/file/WEB43ab8e6d7cff0e1d1d48a177991f9e4d?method=download&shareKey=b4086a495e5665741d5ce836ca6cae90)]

配置登录页面

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }

解析:

  1. loginPage("/login.html") 配置登录页面指向/static/login.html(默认);
  2. 实际上它还有一个隐藏的操作,就是登录接口地址也设置成 /login.html 了。换句话说,新的登录页面和登录接口地址都是 /login.html。他们也可以分开,通过.loginPage("/login.html").loginProcessingUrl("/doLogin").具体逻辑参见 FormLoginConfigurer;

替换security默认登录

static 目录增加 login.html 页面,或者 增加 viewController:

  @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

解析:
默认登录逻辑参看 DefaultLoginPageGeneratingFilter 类的逻辑;

修改登录参数名词

默认参数名称为username,password。通过以下配置修改前端参数名称:

.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/doLogin")
.usernameParameter("name")
.passwordParameter("passwd")
.permitAll()

登录事件回调

成功

  • defaultSuccessUrl: 默认成功页面。如果有历史跳转则跳转历史
  • successForwardUrl: 表示不管你是从哪里来的,登录后一律跳转到 successForwardUrl 指定的地址。

注意:实际操作中,defaultSuccessUrl 和 successForwardUrl 只需要配置一个即可。

失败

注销登录

注销登录的默认接口是 /logout,我们也可以配置。

.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout","POST"))
.logoutSuccessUrl("/index")
.deleteCookies()
.clearAuthentication(true)
.invalidateHttpSession(true)
.permitAll()
.and()

说明:

  • 默认注销的 URL 是 /logout,是一个 GET 请求,我们可以通过 logoutUrl 方法来修改默认的注销 URL。
  • logoutRequestMatcher 方法不仅可以修改注销 URL,还可以修改请求方式,实际项目中,这个方法和 logoutUrl 任意设置一个即可。
  • logoutSuccessUrl 表示注销成功后要跳转的页面。
  • deleteCookies 用来清除 cookie。
  • clearAuthentication 和 invalidateHttpSession 分别表示清除认证信息和使 HttpSession 失效,默认可以不用配置,默认就会清除。

前后端分离,json登录

  1. 自定义登录验证过滤器;
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String verify_code = (String) request.getSession().getAttribute("verify_code");
        if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
            Map<String, String> loginData = new HashMap<>();
            try {
                loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            } catch (IOException e) {
            }finally {
                String code = loginData.get("code");
                checkCode(response, code, verify_code);
            }
            String username = loginData.get(getUsernameParameter());
            String password = loginData.get(getPasswordParameter());
            if (username == null) {
                username = "";
            }
            if (password == null) {
                password = "";
            }
            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    username, password);
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        } else {
            checkCode(response, request.getParameter("code"), verify_code);
            return super.attemptAuthentication(request, response);
        }
    }

    public void checkCode(HttpServletResponse resp, String code, String verify_code) {
        if (code == null || verify_code == null || "".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) {
            //验证码不正确
            throw new AuthenticationServiceException("验证码不正确");
        }
    }
}
  1. 注入bean;
@Bean
LoginFilter loginFilter() throws Exception {
    LoginFilter loginFilter = new LoginFilter();
    loginFilter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            Hr hr = (Hr) authentication.getPrincipal();
            hr.setPassword(null);
            RespBean ok = RespBean.ok("登录成功!", hr);
            String s = new ObjectMapper().writeValueAsString(ok);
            out.write(s);
            out.flush();
            out.close();
        }
    });
    loginFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            RespBean respBean = RespBean.error(exception.getMessage());
            if (exception instanceof LockedException) {
                respBean.setMsg("账户被锁定,请联系管理员!");
            } else if (exception instanceof CredentialsExpiredException) {
                respBean.setMsg("密码过期,请联系管理员!");
            } else if (exception instanceof AccountExpiredException) {
                respBean.setMsg("账户过期,请联系管理员!");
            } else if (exception instanceof DisabledException) {
                respBean.setMsg("账户被禁用,请联系管理员!");
            } else if (exception instanceof BadCredentialsException) {
                respBean.setMsg("用户名或者密码输入错误,请重新输入!");
            }
            out.write(new ObjectMapper().writeValueAsString(respBean));
            out.flush();
            out.close();
        }
    });
    loginFilter.setAuthenticationManager(authenticationManagerBean());
    loginFilter.setFilterProcessesUrl("/doLogin");
    return loginFilter;
}
  1. 替换默认过滤器:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        ...
        //省略
    http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
}

验证码功能

  1. 通过接口获取验证码,同时将验证码放入session 中;
  2. 自定以验证码过滤器(验证输入和session中的是否一致),并将验证码过滤器置于UsernamePasswordAuthenticationFilter之前 ;
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(captchaAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

角色访问

角色权限拦截

http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().authenticated()
        .and()
        ...
        ...

角色权限继承

上级角色一定可以访问下级资源;

@Bean
RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_admin > ROLE_user");
    return hierarchy;
}

注意,在配置时,需要给角色手动加上 ROLE_ 前缀。上面的配置表示 ROLE_admin 自动具备 ROLE_user 的权限。

持久化的用户数据

UserDetailService

Spring Security 支持多种不同的数据源,这些不同的数据源最终都将被封装成 UserDetailsService 的实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mWuw5hk5-1621588647485)(https://note.youdao.com/yws/api/personal/file/WEB638f4f116aaec88dce24e6790a375492?method=download&shareKey=e66b234b4f4246c0f1525fa573b91ba2)]

可以看到,在几个能直接使用的实现类中,除了 InMemoryUserDetailsManager 之外,还有一个 JdbcUserDetailsManager,使用 JdbcUserDetailsManager 可以让我们通过 JDBC 的方式将数据库和 Spring Security 连接起来。

JdbcUserDetailsManager

JdbcUserDetailsManager 自己提供了一个数据库模型,这个数据库模型保存在org/springframework/security/core/userdetails/jdbc/users.ddl;

解析:

  • users 表中保存用户的基本信息,包括用户名、用户密码以及账户是否可用。
  • authorities 中保存了用户的角色。
  • authorities 和 users 通过 username 关联起来。

注入一个userDetailService 替换掉默认的 InMemoryUserDetailsManager:

@Autowired
DataSource dataSource;
@Override
@Bean
protected UserDetailsService userDetailsService() {
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
    manager.setDataSource(dataSource);
    if (!manager.userExists("u0")) {
        manager.createUser(User.withUsername("u0").password("123").roles("admin").build());
    }
    if (!manager.userExists("u1")) {
        manager.createUser(User.withUsername("u1").password("123").roles("user").build());
    }
    return manager;
}

记住用户自动登录

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .and()
            .csrf().disable();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值