SpringBoot - 安全入门与SpringSecurity

市面上有两种常见的安全框架,Shiro和SpringSecurity。功能都很强大,用户数量也都很多。SpringSecurity优势在于能和Spring无缝衔接。

Shiro系列教程:https://blog.csdn.net/j080624/article/category/7006814

Shiro官网地址:https://shiro.apache.org/

Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。


【1】认证和授权

应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。

“认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。

“授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。

这个概念是通用的而不只在Spring Security中。


【2】Spring Security

使用注解@EnableWebSecurity开启WebSecurity模式。

该注解源码如下:

/**
 * Add this annotation to an {@code @Configuration} class to have the Spring Security
 * configuration defined in any {@link WebSecurityConfigurer} or more likely by extending
 * the {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
 *在配置类上添加该注解使其获得Spring Security配置,该配置被定义在任何
 WebSecurityConfigurer中或继承自WebSecurityConfigurerAdapter并重写方法的
 类中,后者如下所示:
 * 
 * @Configuration
 * @EnableWebSecurity
 * public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
 *
 * 	@Override
 * 	public void configure(WebSecurity web) throws Exception {
 * 		web.ignoring()
 * 		// Spring Security should completely ignore URLs starting with /resources/
 * 				.antMatchers("/resources/**");
 * 	}
 *
 * 	@Override
 * 	protected void configure(HttpSecurity http) throws Exception {
 * 		http.authorizeRequests().antMatchers("/public/**").permitAll().anyRequest()
 * 				.hasRole("USER").and()
 * 				// Possibly more configuration ...
 * 				.formLogin() // enable form based log in
 * 				// set permitAll for all URLs associated with Form Login
 * 				.permitAll();
 * 	}
 *
 * 	@Override
 * 	protected void configure(AuthenticationManagerBuilder auth) {
 * 		auth
 * 		// enable in memory based authentication with a user named "user" and "admin"
 * 		.inMemoryAuthentication().withUser("user").password("password").roles("USER")
 * 				.and().withUser("admin").password("password").roles("USER", "ADMIN");
 * 	}
 *
 * 	// Possibly more overridden methods ...
 * }
 * 
* * @see WebSecurityConfigurer * @see WebSecurityConfigurerAdapter * * @author Rob Winch * @since 3.2 */ @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class }) @EnableGlobalAuthentication//开启全局权限配置 @Configuration// 这里引用了@Configuration该注解 public @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false; }

两个重要的类:

WebSecurityConfigurerAdapter:自定义Security策略

AuthenticationManagerBuilder:自定义认证策略


【3】源码与测试

① 基础环境

默认欢迎页面如下(未登录状态),不同权限可以查看不同武功秘籍:

SpringBoot - 安全入门与SpringSecurity_第1张图片


② 引入SpringSecurity

Starter如下:


	org.springframework.boot
	spring-boot-starter-security

自定义Security配置类初始如下:

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    // 如下,定制请求的授权规则
        http.authorizeRequests()
            .antMatchers("/css/**", "/").permitAll()
            .antMatchers("/level1/**").hasRole("VIP1")
            .antMatchers("/level2/**").hasRole("VIP2")
            .antMatchers("/level3/**").hasRole("VIP3")
    }
}

此时再访问任意武林秘籍,则会提示访问被拒绝如下图:

SpringBoot - 安全入门与SpringSecurity_第2张图片


③ 开启自动配置的登录功能

**http.formLogin();**方法会自动创建默认登录页。默认/login请求就会来到登录页,如果登录失败则会重定向到/login?error。也可以自定义登录页,查看HttpSecurity.formLogin()源码示例如下:

/**
	 * Specifies to support form based authentication.
	 * //指定支持基于表单的身份验证。 
	 * If
	 * {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page
	 * will be generated.
	 *// 如果登录页没有指定,则会创建一个默认登录页
	 * 

Example Configurations

* * The most basic configuration defaults to automatically generating a login page at * the URL "/login", redirecting to "/login?error" for authentication failure. The * details of the login page can be found on * {@link FormLoginConfigurer#loginPage(String)} * *
	 * @Configuration
	 * @EnableWebSecurity
	 * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
	 *
	 * 	@Override
	 * 	protected void configure(HttpSecurity http) throws Exception {
	 * 		http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
	 * 	}
	 *
	 * 	@Override
	 * 	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	 * 		auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
	 * 	}
	 * }
	 * 
* * The configuration below demonstrates customizing the defaults. * *
	 * @Configuration
	 * @EnableWebSecurity
	 * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
	 *// 着重观察如下配置示例 !!!
	 * 	@Override
	 * 	protected void configure(HttpSecurity http) throws Exception {
	 * 		http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
	 * 				.usernameParameter("username") // default is username
	 * 				.passwordParameter("password") // default is password
	 * 				.loginPage("/authentication/login") // default is /login with an HTTP get
	 * 				.failureUrl("/authentication/login?failed") // default is /login?error
	 * 				.loginProcessingUrl("/authentication/login/process"); // default is /login
	 * 																		// with an HTTP
	 * 																		// post
	 * 	}
	 *
	 * 	@Override
	 * 	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	 * 		auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
	 * 	}
	 * }
	 * 
* * @see FormLoginConfigurer#loginPage(String) * * @return * @throws Exception */ public FormLoginConfigurer formLogin() throws Exception { return getOrApply(new FormLoginConfigurer()); }

此时还配置任何用户,没有登录没有权限,访问Level1,会跳向默认的登录页:

SpringBoot - 安全入门与SpringSecurity_第3张图片


④ 为系统添加用户、密码和角色

配置类中添加方法:

	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("zhangsan").password("123456").roles("VIP1","VIP2")
                .and()
                .withUser("lisi").password("123456").roles("VIP3","VIP2")
                .and()
                .withUser("wangwu").password("123456").roles("VIP3","VIP1");

    }

该种方法是将用户保存在内存中,项目中应该使用数据库,如下:

	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery("select username,password, enabled from users where username = ?")
                .authoritiesByUsernameQuery("select username, role from user_roles where username = ?");
    }

此时不同用户就拥有了不同权限,如zhangsan只能访问普通和高级武功秘籍,访问绝世武功秘籍就会提示访问被拒绝:

SpringBoot - 安全入门与SpringSecurity_第4张图片


⑤ 开启自动注销功能http.formLogout()

看源码注释:

/**
	 * Provides logout support. This is automatically applied when using
	 * {@link WebSecurityConfigurerAdapter}.
	 * // 提供了注销支持。这种能力是自动被应用的当使用了WebSecurityConfigurerAdapter。
	 *  The default is that accessing the URL
	 * "/logout" will log the user out by invalidating the HTTP Session, cleaning up any
	 * {@link #rememberMe()} authentication that was configured, clearing the
	 * {@link SecurityContextHolder}, and then redirect to "/login?success".
	 *// 默认应用于/logout请求,删除会话session,清除rememberMe权限配置并重定向到/login?success请求。
	 * 

Example Custom Configuration

* * The following customization to log out when the URL "/custom-logout" is invoked. * Log out will remove the cookie named "remove", not invalidate the HttpSession, * clear the SecurityContextHolder, and upon completion redirect to "/logout-success". * *
	 * @Configuration
	 * @EnableWebSecurity
	 * public class LogoutSecurityConfig extends WebSecurityConfigurerAdapter {
	 *
	 * 	@Override
	 * 	protected void configure(HttpSecurity http) throws Exception {
	 * 		http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
	 * 				.and()
	 * 				// sample logout customization
	 * 				.logout().deleteCookies("remove").invalidateHttpSession(false)
	 * 				.logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success");
	 * 	}
	 *
	 * 	@Override
	 * 	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	 * 		auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
	 * 	}
	 * }
	 * 
* * @return * @throws Exception */ public LogoutConfigurer logout() throws Exception { return getOrApply(new LogoutConfigurer()); }

修改请求访问规则配置方法如下:

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 定制请求的授权规则
        http.authorizeRequests()
            .antMatchers("/css/**", "/").permitAll()
            .antMatchers("/level1/**").hasRole("VIP1")
            .antMatchers("/level2/**").hasRole("VIP2")
            .antMatchers("/level3/**").hasRole("VIP3");
        // 开启自动配置的登录功能
        http.formLogin();

        // 开启自动配置的注销功能
        http.logout();
    }

在欢迎页面添加注销表单:

SpringBoot - 安全入门与SpringSecurity_第5张图片

点击注销效果如下(重定向到了/login?logout):

SpringBoot - 安全入门与SpringSecurity_第6张图片


修改默认退出重定向页面:

// 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
http.logout().logoutSuccessUrl("/");

此时退出返回到主页面!


【4】Thymeleaf提供的SpringSecurity标签支持

需要引入thymeleaf-extras-springsecurity4,修改默认引入的Thymeleaf版本:


		UTF-8
		UTF-8
		1.8
		3.0.9.RELEASE
		2.3.0
		3.0.2.RELEASE
	
	
dependency>
	org.thymeleaf.extras
	thymeleaf-extras-springsecurity


页面引入security的名称空间:



① 测试一,修改默认访问页面提示

修改默认访问页面如下:





Insert title here


欢迎光临武林秘籍管理系统

游客您好,如果想查看武林秘籍 请登录

,您好,您的角色有:

没有认证时访问首页如下:

SpringBoot - 安全入门与SpringSecurity_第7张图片


登录成功后,首页显示如下:

SpringBoot - 安全入门与SpringSecurity_第8张图片


② 测试二,不同权限显示不同武林秘籍

修改页面如下:





Insert title here


欢迎光临武林秘籍管理系统

游客您好,如果想查看武林秘籍 请登录

,您好,您的角色有:


普通武功秘籍

高级武功秘籍


没有登录时,显示效果如下:

SpringBoot - 安全入门与SpringSecurity_第9张图片

登录后显示效果如下:

SpringBoot - 安全入门与SpringSecurity_第10张图片

只显示拥有的权限目录!


【5】常见功能-Remeber me

配置启用remember-me功能

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 定制请求的授权规则
        http.authorizeRequests()
            .antMatchers("/css/**", "/").permitAll()
            .antMatchers("/level1/**").hasRole("VIP1")
            .antMatchers("/level2/**").hasRole("VIP2")
            .antMatchers("/level3/**").hasRole("VIP3");
        // 开启自动配置的登录功能
        http.formLogin();

        // 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
        http.logout().logoutSuccessUrl("/");

        //开启自动配置的Remember me
        http.rememberMe();
    }

此时登录页显示如下:

SpringBoot - 安全入门与SpringSecurity_第11张图片


点击登录,查看network :

SpringBoot - 安全入门与SpringSecurity_第12张图片
SpringBoot - 安全入门与SpringSecurity_第13张图片


此时关闭浏览器,再次打开,直接进入登录后的页面:

SpringBoot - 安全入门与SpringSecurity_第14张图片

点击注销,将会从浏览器删除该Cookie!
SpringBoot - 安全入门与SpringSecurity_第15张图片


【6】自定义登录页与Remember-me

① 自定义登录页面

修改配置如下:

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 定制请求的授权规则
        http.authorizeRequests()
            .antMatchers("/css/**", "/").permitAll()
            .antMatchers("/level1/**").hasRole("VIP1")
            .antMatchers("/level2/**").hasRole("VIP2")
            .antMatchers("/level3/**").hasRole("VIP3");
        // 开启自动配置的登录功能
        http.formLogin().loginPage("/userlogin");

        // 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
        http.logout().logoutSuccessUrl("/");

        //开启自动配置的Remember me
        http.rememberMe();
    }

修改自定义登录页面如下:





Insert title here


	

欢迎登陆武林秘籍管理系统


用户名:
密   码:

访问受保护的页面或者点击登录时,会来到我们自定义的登录页面:

SpringBoot - 安全入门与SpringSecurity_第16张图片


FormLoginConfigurer.loginPage方法源码与注释如下:

/**
	 * 

* Specifies the URL to send users to if login is required. * 发送给用户的登录请求 * If used with * {@link WebSecurityConfigurerAdapter} a default login page will be generated when * this attribute is not specified. * 如果没有被指定则使用默认的登录页面 *

* *

* If a URL is specified or this is not being used in conjuction with * {@link WebSecurityConfigurerAdapter}, users are required to process the specified * URL to generate a login page. * 如果url被指定或没有使用默认的登录页面,用户需要处理登录请求跳转到登录页面 * In general, the login page should create a form that * submits a request with the following requirements to work with * {@link UsernamePasswordAuthenticationFilter}: * // 一般来说,登录页面需要创建一个表单提交如下所需的参数属性: *

* *
    *
  • It must be an HTTP POST
  • *
  • It must be submitted to {@link #loginProcessingUrl(String)}
  • *
  • It should include the username as an HTTP parameter by the name of * {@link #usernameParameter(String)}
  • *
  • It should include the password as an HTTP parameter by the name of * {@link #passwordParameter(String)}
  • *
* *

Example login.jsp

* * Login pages can be rendered with any technology you choose so long as the rules * above are followed. Below is an example login.jsp that can be used as a quick start * when using JSP's or as a baseline to translate into another view technology. *下面是一个JSP例子,可以被用来快速创建登录页。 *
	 * 
	 * <c:url value="/login" var="loginProcessingUrl"/>
	 * <form action="${loginProcessingUrl}" method="post">
	 *    <fieldset>
	 *        <legend>Please Login</legend>
	 *        <!-- use param.error assuming FormLoginConfigurer#failureUrl contains the query parameter error -->
	 *        <c:if test="${param.error != null}">
	 *            <div>
	 *                Failed to login.
	 *                <c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}">
	 *                  Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
	 *                </c:if>
	 *            </div>
	 *        </c:if>
	 *        <!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout -->
	 *        <c:if test="${param.logout != null}">
	 *            <div>
	 *                You have been logged out.
	 *            </div>
	 *        </c:if>
	 *        <p>
	 *        <label for="username">Username</label>
	 *        <input type="text" id="username" name="username"/>
	 *        </p>
	 *        <p>
	 *        <label for="password">Password</label>
	 *        <input type="password" id="password" name="password"/>
	 *        </p>
	 *        <!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter -->
	 *        <p>
	 *        <label for="remember-me">Remember Me?</label>
	 *        <input type="checkbox" id="remember-me" name="remember-me"/>
	 *        </p>
	 *        <div>
	 *            <button type="submit" class="btn">Log in</button>
	 *        </div>
	 *    </fieldset>
	 * </form>
	 * 
* *

Impact on other defaults

* * Updating this value, also impacts a number of other default values. * // 更新默认值,将会影响其他默认值 * For example, * the following are the default values when only formLogin() was specified. *// 下面是当formLogin被开启时一系列默认值 *
    *
  • /login GET - the login form
  • *
  • /login POST - process the credentials and if valid authenticate the user
  • *
  • /login?error GET - redirect here for failed authentication attempts
  • *
  • /login?logout GET - redirect here after successfully logging out
  • *
* * If "/authenticate" was passed to this method it update the defaults as shown below: * *
    *
  • /authenticate GET - the login form
  • *
  • /authenticate POST - process the credentials and if valid authenticate the user *
  • *
  • /authenticate?error GET - redirect here for failed authentication attempts
  • *
  • /authenticate?logout GET - redirect here after successfully logging out
  • *
* * * @param loginPage the login page to redirect to if authentication is required (i.e. * "/login") * @return the {@link FormLoginConfigurer} for additional customization */ @Override public FormLoginConfigurer loginPage(String loginPage) { return super.loginPage(loginPage); }

通过阅读上述源码,我们尝试修改用户名、密码默认参数名如下:

  • 修改配置类
@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 定制请求的授权规则
        http.authorizeRequests()
            .antMatchers("/css/**", "/").permitAll()
            .antMatchers("/level1/**").hasRole("VIP1")
            .antMatchers("/level2/**").hasRole("VIP2")
            .antMatchers("/level3/**").hasRole("VIP3");
        // 开启自动配置的登录功能
        http.formLogin().usernameParameter("name").passwordParameter("pwd").loginPage("/userlogin");

        // 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
        http.logout().logoutSuccessUrl("/");

        //开启自动配置的Remember me
        http.rememberMe();
    }
  • 修改表单




Insert title here


	

欢迎登陆武林秘籍管理系统


用户名:
密   码:

再次测试登录:

SpringBoot - 安全入门与SpringSecurity_第17张图片
成功登录!


② 为自定义登录页面添加Remember-me

首先查看使用默认登录页面时remember-me的源码:

SpringBoot - 安全入门与SpringSecurity_第18张图片

可以看到,添加了一个复选框,name为"remember-me"。

同样,在我们的登录表单中,也添加该复选框即可!

修改表单如下:

用户名:
密   码:
Remember-me

登录测试如下图:

SpringBoot - 安全入门与SpringSecurity_第19张图片
SpringBoot - 安全入门与SpringSecurity_第20张图片


同样可以查看RememberMeConfigurer源码获取记住我的默认name:

public final class RememberMeConfigurer>
		extends AbstractHttpConfigurer, H> {
	/**
	 * The default name for remember me parameter name and remember me cookie name
	 */
	private static final String DEFAULT_REMEMBER_ME_NAME = "remember-me";
	private AuthenticationSuccessHandler authenticationSuccessHandler;
	private String key;
	private RememberMeServices rememberMeServices;
	private LogoutHandler logoutHandler;
	private String rememberMeParameter = DEFAULT_REMEMBER_ME_NAME;
	private String rememberMeCookieName = DEFAULT_REMEMBER_ME_NAME;
	private String rememberMeCookieDomain;
	private PersistentTokenRepository tokenRepository;
	private UserDetailsService userDetailsService;
	private Integer tokenValiditySeconds;
	private Boolean useSecureCookie;
	private Boolean alwaysRemember;
	//...
}

也可以使用自定义记住我name:

  • 配置方法修改
  //开启自动配置的Remember me,并修改记住我的参数name
  http.rememberMe().rememberMeParameter("remember");
  • 表单修改
用户名:
密   码:
Remember-me

【7】CSRF

CSRF(Cross-site request forgery)跨站请求伪造。

HttpSecurity默认启用csrf功能,会为表单添加_csrf的值,提交携带来预防CSRF。

如点击登录,查看network:

SpringBoot - 安全入门与SpringSecurity_第21张图片

本篇博文项目地址:https://github.com/JanusJ/SpringBoot/tree/master/security

你可能感兴趣的:(SpringBoot)