在第八章的基础上进行开发
一、建立角色与资源的关系
二、CSRF问题的解决
三、启用方法级别的安全设置
四、使用BCrypt加密密码
五、用户登录
六、记住我
在UserServiceImpl中实现UserDetailsService接口,重写方法
//通过用户账号加载用户的认证信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
SecurityConfig类进行完善如下
package com.waylau.spring.boot.blog.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 安全配置类
*/
@EnableWebSecurity//启用web安全
@EnableGlobalMethodSecurity(prePostEnabled = true)//启用方法安全设置
public class SecurityConfig extends WebSecurityConfigurerAdapter{
private static final String KEY = "waylau.com";
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();//使用BCrypt加密
}
@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);//设置密码加密方式
return authenticationProvider;
}
/**
* 自定义配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll()//都可以访问
.antMatchers("/h2-console/**").permitAll()//都可以访问
.antMatchers("/admins/**").hasRole("ADMIN")//需要相应的角色才可以可以访问
.and()
.formLogin()//基于Form表单登录验证
.loginPage("/login").failureUrl("/login-error")//自定义登录界面 登录失败会重定向到/login-error
.and().rememberMe().key(KEY)//启用remenber me
.and().exceptionHandling().accessDeniedPage("/403");//处理异常 拒绝访问就重定向到/403
http.csrf().ignoringAntMatchers("/h2-console/**");//禁用H2控制台的CSRF防护
http.headers().frameOptions().sameOrigin();//允许来自统一来源的H2控制台请求
}
/**
* 认证信息的管理
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService);//认证信息存储于数据库中
auth.authenticationProvider(authenticationProvider());
}
}
由于配置中启用了CSRF防护,也就是说只有GET|HEAD|TRACE|OPTIONS这4类方法会被放行,其它Method的http请求(例如POST请求)都要验证_csrf的token是否正确
若没有携带该token,那么请求会被拒绝,被认为是非法的请求 Form表单会自动携带csrf的token
下面完善登录功能
在header.html的header中添加两个标签如下,该header.html会被各个页面引入使用
<!-- CSRF -->
<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
以及如下(未认证时候显示登录和注册 认证后显示个人主页、设置等)
<nav class="navbar navbar-inverse bg-inverse navbar-toggleable-md">
<div class="container">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
data-target="#navbarsContainer" aria-controls="navbarsExampleContainer" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="/" th:href="@{/}">NewStarBlog</a>
<div class="collapse navbar-collapse" id="navbarsContainer">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/blogs?order=new">最新 <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/blogs?order=hot">最热</a>
</li>
<li>
<form class="form-inline mt-2 mt-md-0">
<input class="form-control mr-sm-2" type="text" placeholder="搜索">
<a href="/blogs?keyword=ww" class="btn btn-outline-secondary my-2 my-sm-0"><i class="fa fa-search" aria-hidden="true"></i></a>
</form>
</li>
</ul>
<!-- isAuthenticated() 判断是否认证 -->
<div sec:authorize="isAuthenticated()" class="row" >
<div class="dropdown">
<a class=" dropdown-toggle" href="/u/waylau" th:href="@{'/u/' + ${#authentication.name}}" data-toggle="dropdown"><span sec:authentication="name"></span></a>
<div class="dropdown-menu" >
<a class=" dropdown-item" href="/u/waylau" th:href="@{'/u/' + ${#authentication.name}}">个人主页</a>
<a class="dropdown-item" href="/u/waylau/profile" th:href="@{'/u/' + ${#authentication.name}} + '/profile'" >个人设置</a>
</div>
</div>
<div>
<a href="/u/waylau/blogs/edit" th:href="'/u/' + ${#authentication.name} + '/blogs/edit'" class="btn btn-outline-success my-2 my-sm-0">写博客</a>
</div>
<form action="/logout" th:action="@{/logout}" method="post">
<input class="btn btn-outline-success " type="submit" value="退出">
</form>
</div>
<!-- 是否未认证 -->
<div sec:authorize="isAnonymous()">
<a href="/login" class="btn btn-outline-success my-2 my-sm-0" type="submit">登录</a>
<a href="/register" class="btn btn-outline-success my-2 my-sm-0" type="submit">注册</a>
</div>
</div>
</div>
</nav>
再修改启动时执行的sql(主要是修改密码 此时密码已经加密)
INSERT INTO user (id, username, password, name, email) VALUES (1, 'admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', '老卫', 'i@waylau.com');
INSERT INTO user (id, username, password, name, email) VALUES (2, 'waylau', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'Way Lau', 'waylau@waylau.com');
INSERT INTO authority (id, name) VALUES (1, 'ROLE_ADMIN');
INSERT INTO authority (id, name) VALUES (2, 'ROLE_USER');
INSERT INTO user_authority (user_id, authority_id) VALUES (1, 1);
INSERT INTO user_authority (user_id, authority_id) VALUES (2, 2);
启动springboot 访问地址如下 http://localhost:8080/admins 由于此时未登录,会重定向到login页面
这时使用一个普通账号登录 waylau 123456
显示页面如下
因为waylau 是ROLE_USER角色 没有/admins的访问权限
退出后使用admin账号登录
页面显示如下
此时具有访问权限
登录页面中的 记住我
<input type="checkbox" id="remember-me" name="remember-me"><label for="remember-me"> 记住我</label>
当选中后,登录 直接关闭浏览器,再次打开浏览器,则为已登录状态