SpringSecurity(安全)

在web开发中,安全第一位!过滤器,拦截器~

做网站:安全应该在什么时候考虑?设计之初

  • 漏洞,隐私泄露
  • 架构一旦确定

shiro,springsecurity:很像~除了类不一样,名字也不一样

认证,授权(user,vip,svip),如实现超级会员和会员以及普通用户区分这一需求

  • 功能权限
  • 访问权限
  • 菜单权限
  • …拦截器,过滤器:大量的原生代码~冗余

SpringSecurity整合了
MVC-Spring-Springboot-框架思想
实现了Aop:横切~配置类以及实现了拦截过滤的作用,可以用于权限管理

简介

Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架。凭借对命令式和响应式应用程序的一流支持,它是用于保护基于Spring的应用程序的事实上的标准。

Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenicationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

SpringSecurity的两个主要目标是“认证”和“授权”(访问控制)

特征

  • 对身份验证和授权的全面且可扩展的支持
  • 防止攻击,例如会话固定,点击劫持,跨站点请求伪造等
  • Servlet API集成
  • 与Spring Web MVC的可选集成

使用方法

文档里有很多使用方法,我们这使用springboot的。

在springboot的Maven项目中使用的启动器名字为:spring-boot-starter-security(其他方法参考上面文档地址,如果访问不到自己租买加速器吧)

pom.xml里导入:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>

使用时,创建一个java配置类SecurityConfig继承WebSecurityConfigurerAdapter并且加上**@EnableWebSecurity**开启MVC security安全支持,然后在idea使用快捷键AIT+INSERT选择Override Methods选择重写方法,或者直接Ctrl+o可以直接看见这个父类Web安全适配器的方法有哪些,里面configure有哪个方法使用,程序通过调用他们进行重载完成授权和认证。这里面最重要的就是其中两个方法一个是参数为HttpSecurity http的方法和AuthenticationManagerBuilder auth的方法。

故继承之后==》

1.重写configure(HttpSecurity http)方法,进行用户授权管理
2.重写configure(AuthenticationManagerBuilder auth)方法,进行自定义用户认证

当然如果感觉重写方法无从下手,在idea中请我们可以选择参数的类型使用快捷键Ctrl+B进入方法,可以看见http或者auth的父类有有哪些方法可以用,看见idea提示说下载源码,点击下载源码这样可以看见注释,注释里有方法使用的样例还有英文注解。

两个方法都采取的是链式的表达式。

相对通用的模板

大家结合上面方法点进源码模仿写:

以下给出一个比较通用的模板:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;

@EnableWebSecurity  // 开启MVC security安全支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Value("${COOKIE.VALIDITY}")
    private Integer COOKIE_VALIDITY;

    /**
     * 重写configure(HttpSecurity http)方法,进行用户授权管理
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 1、自定义用户访问控制
        http.authorizeRequests()
                .antMatchers("/","/page/**","/article/**","/login").permitAll()
                .antMatchers("/back/**","/assets/**","/user/**","/article_img/**").permitAll()
                .antMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated();
        // 2、自定义用户登录控制
        http.formLogin()
                .loginPage("/login")
                .usernameParameter("username").passwordParameter("password")
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        String url = httpServletRequest.getParameter("url");
                        // 获取被拦截的原始访问路径
                        RequestCache requestCache = new HttpSessionRequestCache();
                        SavedRequest savedRequest = requestCache.getRequest(httpServletRequest,httpServletResponse);
                        if(savedRequest !=null){
                            // 如果存在原始拦截路径,登录成功后重定向到原始访问路径
                            httpServletResponse.sendRedirect(savedRequest.getRedirectUrl());
                        } else if(url != null && !url.equals("")){
                            // 跳转到之前所在页面
                            URL fullURL = new URL(url);
                            httpServletResponse.sendRedirect(fullURL.getPath());
                        }else {
                            // 直接登录的用户,根据用户角色分别重定向到后台首页和前台首页
                            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                            boolean isAdmin = authorities.contains(new SimpleGrantedAuthority("ROLE_admin"));
                            if(isAdmin){
                                httpServletResponse.sendRedirect("/admin");
                            }else {
                                httpServletResponse.sendRedirect("/");
                            }
                        }
                    }
                })
                // 用户登录失败处理
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        // 登录失败后,取出原始页面url并追加在重定向路径上
                        String url = httpServletRequest.getParameter("url");
                        httpServletResponse.sendRedirect("/login?error&url="+url);
                    }
                });
        // 3、设置用户登录后cookie有效期,默认值
        http.rememberMe().alwaysRemember(true).tokenValiditySeconds(COOKIE_VALIDITY);
        // 4、自定义用户退出控制
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/");
        // 5、针对访问无权限页面出现的403页面进行定制处理
        http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                // 如果是权限访问异常,则进行拦截到指定错误页面
                RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher("/errorPage/comm/error_403");
                dispatcher.forward(httpServletRequest, httpServletResponse);
            }
        });
    }

    /**
     * 重写configure(AuthenticationManagerBuilder auth)方法,进行自定义用户认证
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //  密码需要设置编码器
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        //  使用JDBC进行身份认证
        String userSQL ="select username,password,valid from t_user where username = ?";
        String authoritySQL ="select u.username,a.authority from t_user u,t_authority a," +
                             "t_user_authority ua where ua.user_id=u.id " +
                             "and ua.authority_id=a.id and u.username =?";
        auth.jdbcAuthentication().passwordEncoder(encoder)
                .dataSource(dataSource)
                .usersByUsernameQuery(userSQL)
                .authoritiesByUsernameQuery(authoritySQL);
    }
}

一些坑

接下来就是一些注意使用注意事项:

如果使用SpringSecurity和themleaf整合记得加入整合包以及前端写的时候,sec:authorize="hasRole(‘admin’)"sec属性写在href跳转标签前面才能生效。整合包也要注意版本,springboot新版本就导入新的,2.3.1可用包如下:

<!-- thymeleaf模板整合security控制页面安全访问依赖 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

前端写法:

<a  class="header-info" sec:authorize="hasRole('admin')" href="/admin">后台管理</a>

退出功能,前端可以在上面模板下可以直接在前端使用loginout,而不需要写Controller方法,这个是本框架自带的模板,即上代码在http里的这一段

// 4、自定义用户退出控制
http.logout().logoutUrl("/logout").logoutSuccessUrl("/");

同时由于默认开启CSRF,所以记住做这些操作的时候要用表单标签以及post,不要用get方法,

<form name="logoutform" th:action="@{/logout}" method="post"></form>

又或者在html的标签中指定Token和请求头Header是带有_csrf的,还有Ajax请求也一样!!!望注意!!!

登录功能如果不想设置为login而是让其他走其他页面为login的话,如使用tologin代码如下:

http.formLogin()
        .loginPage("/tologin")
        .loginProcessingUrl("login")
        .usernameParameter("username").passwordParameter("password")

同时注意.usernameParameter(“username”).passwordParameter(“password”)这两个参数也是默认参数,这里需要和你前端的name参数一致,这里也是坑,一不留神就浪费大量的时间,请记住!!