SpringBoot 集成 Spring Securtiy 安全框架
1、导入依赖
<!-- spring security 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- thymeleaf结合security实现页面控制的启动器 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
需要说明的是:一旦项目引入spring-boot-starter-security启动器,MVC Security和WebFlux Security负责的安全功能都会立即生效(WebFlux Security生效的另一个前提是项目属于WebFlux Web项目);对于OAuth2安全管理功能来说,则还需要额外引入一些其他安全依赖。
注意:依赖导入成功后,项目启动时会在控制台Console中自动生成一个安全密码(security password,这个密码在每次启动项目时都是随机生成的)。其中用户名为user,密码随机生成,访问,执行“http://localhost:8080/”访问项目首页,自动跳转到了一个新的登录链接页面“http://localhost:8080/login”
2、自定义用户登录控制
通过前面几个示例演示可以发现,项目中并没有配置用户登录页面和登录处理方法,但是演示过程中却 提供了一个默认的用户登录页面,并且进行了自动登录处理,这就是Spring Security提供的默认登录处理机制。
// 继承WebSecurityConfigurerAdapter对象重写configure(HttpSecurity http)方法实现用户登录控制
/**
* 自定义用户认证,调用时机:在登录时进行验证
* @param auth
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 1、密码需要设置编码器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 2、使用JDBC进行身份认证
String userSQL = "select username,password,valid from t_customer " +
"where username = ?";
String authoritySQL = "select c.username,a.authority from t_customer c, " +
"t_authority a,t_customer_authority ca where " +
"ca.customer_id=c.id and ca.authority_id=a.id and c.username =?";
auth.jdbcAuthentication().passwordEncoder(encoder) /*设置密码的编码格式*/
.dataSource(dataSource) /*设置数据源*/
.usersByUsernameQuery(userSQL) /*访问数据库,判断登录是否成功*/
.authoritiesByUsernameQuery(authoritySQL); /*登录成功后,访问数据库,将用户对象以及权限信息保存进安全框架*/
}
formLogin()用户登录方法涉及到用户登录的主要方法及说明如表所示
- loginPage(String loginPage) 用户登录页面跳转路径,默认为get 请求的/login
- successForwardUrl(String forwardUrl) 用户登录成功后的重定向地址
- successHandler(AuthenticationSuccessHandler successHandler) 用户登录成功后的处理
- defaultSuccessUrl(String defaultSuccessUrl) 用户直接登录后默认跳转地址
- failureForwardUrl(String forwardUrl) 用户登录失败后的重定向地址
- failureUrl(String authenticationFailureUrl) 用户登录失败后的跳转地址,默认为/login?error
- failureHandler(AuthenticationFailureHandler authenticationFailureHandler) 用户登录失败后的错误处理
- usernameParameter(String usernameParameter) 登录用户的用户名参数,默认为username
- passwordParameter(String passwordParameter) 登录用户的密码参数,默认为password
- loginProcessingUrl(String loginProcessingUrl) 登录表单提交的路径,默认为post 请求的/login
- permitAll() 无条件对请求进行放行
3、自定义用户注销控制
自定义用户退出主要考虑退出后的会话如何管理以及跳转到哪个页面。HttpSecurity类的logout()方法用来处理用户退出,它默认处理路径为“/logout”的Post类型请求,同时也会清除Session和“RememberMe(记住我)”等任何默认用户配置。
页面中定义好用户退出链接后,不需要在Controller控制层中额外定义用户退出方法,可以直接在Security中定制logout()方法实现用户退出。打开SecurityConfig类,重写configure(HttpSecurity http) 方法进行用户退出控制,示例代码如下。
// 自定义用户退出控制
http.logout()
.logoutUrl("/mylogout")
.logoutSuccessUrl("/");
上述代码中,在configure(HttpSecurity http)方法中使用logout()及其相关方法实现了用户退出功能。其中,logoutUrl("/mylogout")方法指定了用户退出的请求路径,这个与index.html页面退出表单中aciton的值必须保持一致,如果退出表单中使用了默认的“/logout”请求,则此方法可以省略;logoutSuccessUrl("/")方法指定了用户退出成功后重定向到“/”地址(即再次重定向到项目首页)。在用户退出后,用户会话信息则会默认清除,此情况下无需手动配置(如有需要,读者可自行定制)。
logout()用户退出方法涉及到用户退出的主要方法及说明如表所示。
- logoutUrl(String logoutUrl) 用户退出处理控制URL,默认为post请求的/logout
- logoutSuccessUrl(String logoutSuccessUrl) 用户退出成功后的重定向地址
- logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) 用户退出成功后的处理器设置
- deleteCookies(String… cookieNamesToClear) 用户退出后删除指定Cookie
- invalidateHttpSession(boolean invalidateHttpSession) 用户退出后是否立即清除Session(默认为true)
- clearAuthentication(boolean clearAuthentication) 用户退出后是否立即清除Authentication用户认证信息(默认为true)
4、Spring Secruity标签库
<%@ taglib prefix='security ’ uri=‘http://www.springframework.org/security /tags’ %>
是一个流程控制标签,能够在满足特定安全需求的条件下显示它的内容体。它有三个互斥的参数:
ifAllGranted —— 是一个由逗号分隔的权限列表,用户必须拥有所有列出的权限时显示;
ifAnyGranted —— 是一个由逗号分隔的权限列表,用户必须至少拥有其中的一个权限时才能显示;
ifNotGranted —— 是一个由逗号分隔的权限列表,用户未拥有所有列出的权限时才能显示。
获得属性的值比如要获得用户名可以这么写:
</security :authentication>
他有三个属性,property是必须的,另外scope和var,var定义一个变量,scope定义var存在的范围
5、Spring Secruity后台获取登录用户对象
@Controller
@RequestMapping("Action程序的主路径")
public class MessageAction {
@Secured(value = "ROLE_USER") //表示只能是ROLE_USER角色才能跳转访问此方法,否则跳转到403.jsp页面进行显示
@RequestMapping("方法的访问路径")
public void insert(String pic) {
//表示取得了配置文件中的用户信息
UserDetails userDetails = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println("[当前用户]" + userDetails.getUsername());
System.out.println("[登录密码]" + userDetails.getPassword()); //无法取得,请在网上查询资料
System.out.println("[用户角色]" + userDetails.getAuthorities( ));
}
@Secured(value ={"ROLE_ADMIN","ROLE_USER"}) //表示只能是ROLE_ADMIN和ROLE_USER角色都能跳转访问此方法
@RequestMapping("方法的访问路径")
public void Update(String pic) {}
后台安全逻辑
package cn.mldn.lxh.xmz.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.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.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;
/**
* 自定义用户认证,调用时机:在登录时进行验证
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 1、密码需要设置编码器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 2、使用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); /*登录成功后,访问数据库,将用户对象以及权限信息保存进安全框架*/
}
/**
* 自定义用户登录控制,调用时机:在授权时进行调用,判断是否有相应的权限
*
* @param http 安全管理对象
*/
@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 {
/*获取request域中的参数*/
String url = httpServletRequest.getParameter("url");
// 登录成功后,获取被拦截登录的原始访问路径,
// security安全框架的缓存机制:在未登录的情况下,访问受保护的资源时,会缓存请求的url地址,
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(httpServletRequest,
httpServletResponse);
System.out.println("saveRequest=" + savedRequest.getRedirectUrl());
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,
org.springframework.security.access.AccessDeniedException e)
throws IOException, ServletException {
// 如果是权限访问异常,则进行拦截到指定错误页面
httpServletRequest.getRequestDispatcher("/errorPage/comm/error_403")
.forward(httpServletRequest, httpServletResponse);
}
});
}
}
前台
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<!--导入thymeleaf模块引擎,和springSecurity安全框架命名空间-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>影视直播厅</title>
</head>
<body>
<h1 align="center">欢迎进入电影网站首页</h1>
<hr>
<h3>普通电影</h3>
<!-- 判断是否未登录,未登录为true,否则为false -->
<div sec:authorize="isAnonymous()">
<h2 align="center">游客您好,如果想查看电影<a th:href="@{/userLogin}">请登录</a>
</h2>
</div>
<!--判断是否已登录,已登录为true,否则为false-->
<div sec:authorize="isAuthenticated()">
<h2 align="center">
<span sec:authentication="name" style="color: #007bff"></span>
您好,您的用户权限为<span sec:authentication="principal.authorities"
style="color:darkkhaki"></span>,您有权观看以下电影
</h2>
<form th:action="@{/mylogout}" method="post">
<input th:type="submit" th:value="注销"/>
</form>
</div>
<!--判断是否有指定的角色,有则为true,否则false-->
<div sec:authorize="hasRole('ROLE_common')">
<h3>普通电影</h3>
<ul>
<li><a th:href="@{/detail/common/1}">我不是药神</a></li>
<li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
</ul>
</div>
<div sec:authorize="hasAuthority('ROLE_vip')">
<h3>VIP专享</h3>
<ul>
<li><a th:href="@{/detail/vip/1}">速度与激情</a></li>
<li><a th:href="@{/detail/vip/2}">猩球崛起</a></li>
</ul>
</div>
</body>
</html>