2020:0705--19--SpringBoot和安全

349 阅读7分钟

主要内容

1.  安全
2.  Spring Security

1 概述

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

几个类:
    WebSecurityConfigurerAdapter:自定义Security策略
    AuthenticationManagerBuilder:自定义认证策略
    @EnableWebSecurity:开启WebSecurity模式

概念:
    应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。
    这两个主要区域是Spring Security 的两个目标。
    
    “认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,
    设备或一些可以在你的应用程序中执行动作的其他系统)。
        
        登录验证。
    
    “授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。
    为了抵达需要授权的店,主体的身份已经有认证过程建立。
    
        对某种资源是否能访问。
    
    这个概念是通用的而不只在Spring Security中。

1.  先创建一个没有Security相关模块的工程

2 登录,认证,授权

注意: 编写代码:仿照QuickStart的案例写

    /**
     * @author Joe Grandja
     */
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    	// @formatter:off
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http
    				.authorizeRequests(authorize -> authorize
    					.antMatchers("/css/**", "/index").permitAll()
    					.antMatchers("/user/**").hasRole("USER")
    				)
    				.formLogin(formLogin -> formLogin
    					.loginPage("/login")
    					.failureUrl("/login-error")
    				);
    	}
    	// @formatter:on
    
    	@Bean
    	public UserDetailsService userDetailsService() {
    		UserDetails userDetails = User.withDefaultPasswordEncoder()
    				.username("user")
    				.password("password")
    				.roles("USER")
    				.build();
    		return new InMemoryUserDetailsManager(userDetails);
    	}
    }
1.  引入SpringSecurity
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
2.  编写SpringSecurity的配置类

`@EnableWebSecurity //这个注解带了个@Configuration 所以不用再为这个配置类加上该注解

public class MySecurityConfig extends WebSecurityConfigurerAdapter`

3.  定义授权规则:重写configure()
@EnableWebSecurity //这个注解带了个@Configuration 所以不用再为这个配置类加上该注解
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    //自己定义授权规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);不用父类的规则,我们自己定义授权规则
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("VIP1")
                .antMatchers("/level2/**").hasRole("VIP2")
                .antMatchers("/level3/**").hasRole("VIP3");

        //开启自动配置的登录功能:如果没有登录,没有权限就来到登录页面
        http.formLogin();
        //1.    Login来到登录页
        //2.    重定向到/Login?error表示登录失败
        //3.    更多详细规则

    }
}
4.  定义认证规则:重写userDetailsService()
    @Bean
    protected UserDetailsService userDetailsService() {

        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("zhangsan").password("123456").roles("VIP1","VIP2").build();

        UserDetails userDetails1 = User.withDefaultPasswordEncoder()
                .username("lisi").password("123456").roles("VIP1", "VIP3").build();

        UserDetails userDetails2 = User.withDefaultPasswordEncoder()
                .username("wangwu").password("123456").roles("VIP1","VIP2", "VIP3").build();

        //InMemoryUserDetailsManager() : 有一个构造可以传若干个UserDetails。
        return new InMemoryUserDetailsManager(userDetails, userDetails1, userDetails2);
    }

注意:怎么找到官方文档的QuickStart

5.  测试:其他资源代码,直接粘贴了老师的。

    测试成功

3 注销功能

1.  开启自动配置的注销功能:写在我们自定义的SpringSecurity的配置类中

    http.logout();


2.  在登录页面写一个 退出 表达

    注意:  退出必须POST提交:security的规定
    <!--退出必须POST提交:security的规定-->
    <form th:action="@{/logout}" method="post">
    	<input type="submit" value="注销"/>
    </form>
3.  测试一下:

    注销成功并且来带http://localhost:8080/login?logout 页面
    
4.  定制注销成功后返回的页面:注销成功后,来到首页

    http.logout().logoutSuccessUrl("/"); 
    
5.  测试一下

    测试成功,但是在首页点注销,跳转到首页。
    这样页面没有提示,不太好。

6.  改进:
    
    根据角色权限显示菜单
    
    登陆成功后:不显示 请登录
    
    没有登录时:不显示注销

要先引入SpirngSecurity和Thymeleaf的整合模块,才能使用Thymeleaf对SpirngSecurity支持

1.  引入了SpirngSecurity和Thymeleaf的整合模块
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
2.  在页面:引入相关的名称空间。

    注意我们当前项目导入的springsecurity版本是5
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf/org/thymeleaf-extras-springsecurity5">
3.  编写index.html页面
    <h1 align="center">欢迎光临武林秘籍管理系统</h1>
    
    <!--没有认证时显示:-->
    <div sec:authorize="!isAuthenticated()">
    	<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录</a></h2>
    </div>
    
    <!--认证了显示:-->
    <div sec:authorize="isAuthenticated()">
    	<!--显示登陆名和权限-->
    	<h2><span sec:authentication="name"></span>,你好,你的角色是有:
    		<span sec:authentication="principal.authorities"></span>
    	</h2>
    	<!--退出必须POST提交:security的规定-->
    	<form th:action="@{/logout}" method="post">
    		<input type="submit" value="注销"/>
    	</form>
    </div>
4.  测试

5.  根据权限显示不同菜单
<div sec:authorize="hashRole('VIP1')">
	<h3>普通武功秘籍</h3>
	<ul>
		<li><a th:href="@{/level1/1}">罗汉拳</a></li>
		<li><a th:href="@{/level1/2}">武当长拳</a></li>
		<li><a th:href="@{/level1/3}">全真剑法</a></li>
	</ul>	
</div>

<div sec:authorize="hashRole('VIP2')">
<h3>高级武功秘籍</h3>
<ul>
	<li><a th:href="@{/level2/1}">太极拳</a></li>
	<li><a th:href="@{/level2/2}">七伤拳</a></li>
	<li><a th:href="@{/level2/3}">梯云纵</a></li>
</ul>
</div>

<div sec:authorize="hashRole('VIP3')">
<h3>绝世武功秘籍</h3>
<ul>
	<li><a th:href="@{/level3/1}">葵花宝典</a></li>
	<li><a th:href="@{/level3/2}">龟派气功</a></li>
	<li><a th:href="@{/level3/3}">独孤九剑</a></li>
</ul>
</div>
6.  测试:没有登录时

    测试登录:Role:VIP1 VIP2    

    测试成功

5 记住我

    服务开启时.
    
    当浏览器关闭后,再打开页面就需要重新登录。
    
    我们实现浏览器关闭后,再打开页面不需要登录。
    
    在我们写的自定义SpringSecurity的配置类:开启
@EnableWebSecurity //这个注解带了个@Configuration 所以不用再为这个配置类加上该注解
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    //自己定义授权规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
        ...
        
        //开启自动配置的记住我功能
        http.rememberMe();
    
    }

原理:
    勾选了记住我后,在我们登陆成功后SpringSecurity会为浏览器发送一个cookies,cookies名为
    rememeber-me。

    它的过期时间是7-21号(现在是7/7)

    所以这14天以内,只要我们再次访问该页面,就不需要在登录。
    
    点击注销时,会将cookies删除。

6 定制登录页面

    在自定义的SpringSecurity配置类中,用我们自己的login页面
    
    配置http.formLogin();

        //开启自动配置的登录功能:如果没有登录,没有权限就来到登录页面
        http.formLogin().loginPage("/userlogin");
    同时修改welcome.html页面中的请求路径:
<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/userlogin}">请登录</a></h2>
    通过/userlogin请求,会被controller中的方法处理,然后来到我们自定义的登录页面
    /pages/login.html。
    
    那么这个login.html肯定有一个提交表单的请求:怎么将这些数据给SpringSecurity让它来处理呢。
    

    原来SpringSecurity:
        默认如果有/login请求且是POST:那它就会去处理认证的。
    
    我们把login.html的页面的提交请求设置为/login
    
    但是现在 http.formLogin().loginPage("/userlogin"); 
        一旦定制loginPage,那么loginPage中指定的/userlogin POST请求就被当做了登陆验。
        
    我们可以将login.html的页面的提交请求设置为/userlogin,
        
        同时在指定一下用户名和密码等参数传给springsecurity
        1.  login.html
        <!--默认POST形式的/login代表处理登陆:SpringSecurity回去处理-->
		<form th:action="@{/userlogin}" method="post">
			用户名:<input name="user"/><br>
			密码:<input name="pwd"><br/>
			<input type="submit" value="登陆">
		</form>
        
        2.  配置类
        http.formLogin().usernameParameter("user").passwordParameter("pwd")
                .loginPage("/userlogin")
    再给登录页面加一个记住我复选框

        //参数:remember传给登陆页面
        http.rememberMe().rememberMeParameter("remember");
        
        
		<form th:action="@{/userlogin}" method="post">
			用户名:<input name="user"/><br>
			密码:<input name="pwd"><br/>
			<input type="checkbox" name="remember">记住我<br/>
			<input type="submit" value="登陆">
		</form>
	
  测试成功。