项目架构:

SpringBoot 工程
springboot版本 2.1.3
SSM 架构 + Mysql 数据库 + Maven
前端界面 使用的是jsp 并引用了 bootstrap

一:引入Spring Security安全框架

pom.xml 加入引用

<!-- Spring Security 权限管理框架 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

二:默认拦截

引入Spring Security安全框架 ,

启动项目后,访问项目 会自动拦截到默认的登录界面(自带的)

spring boot actuator 安全 spring boot安全框架_spring


在控制台和日志中会出现默认密码, 默认帐号user

spring boot actuator 安全 spring boot安全框架_java_02

登录后,跳转到真实访问的界面。
引入spring Security 后,不想开启 拦截校验,可以在项目主方法上加上
exclude= SecurityAutoConfiguration.class

//关闭Security 登录验证功能,Security5.x 后设置
@SpringBootApplication(exclude= SecurityAutoConfiguration.class)

三:自定义各类拦截

实现自定义拦截的关键在于 WebSecurityConfigurerAdapter 类
此类提供各类的配置方法

//WebSecurityConfigurerAdapter 类 部分源码
public abstract class WebSecurityConfigurerAdapter implements
		WebSecurityConfigurer<WebSecurity> {
		
		//由默认实现{@link #authenticationManager()},可以查看源码的注释,对理解使用有很大帮助 eg:实现权限控制,校验帐号密码
		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
			this.disableLocalConfigureAuthenticationBldr = true;
	    }
		//重写此方法来配置{@link WebSecurity}。eg:忽略某些请求
       public void configure(WebSecurity web) throws Exception {
	   }

	   //覆盖这个方法来配置{@link HttpSecurity},eg:通过重写,配置http来进行拦截的配置
       protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

}

首先新建继承 WebSecurityConfigurerAdapter 的配置类

1.基于内存的方式 来拦截

@Configuration
@EnableWebSecurity//开启Spring Security的功能
//prePostEnabled属性决定Spring Security在接口前注解是否可用
//@PreAuthorize,@PostAuthorize等注解,设置为true,
//会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfg extends WebSecurityConfigurerAdapter{
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//super.configure(auth);
		System.out.println("WebSecurityConfg AuthenticationManagerBuilder");
		// 基于内存的方式,
		//创建两个用户admin/123456 ,user/123456 可以使用这两个用户登录
		auth.inMemoryAuthentication()
		.withUser("admin")//用户名
		.password(passwordEncoder().encode("123456"))//密码
		.roles("ADMIN");//角色
		
		auth.inMemoryAuthentication()
        .withUser("user")//用户名
        .password(passwordEncoder().encode("123456"))//密码
        .roles("USER");//角色
	
	}
 	@Bean
    public PasswordEncoder passwordEncoder(){
        // 使用BCrypt加密密码
        return new BCryptPasswordEncoder();
    }
}

2.查询数据库 用户数据进行 拦截

(1)最关键的是实现 org.springframework.security.core.userdetails.UserDetailsService 接口
加载用户特定数据的核心接口。
它作为一个用户DAO在整个框架中使用,也是DaoAuthenticationProvider使用的策略。
该接口只需要一个只读方法,这简化了对新的数据访问策略的支持。

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.myjz.clansman.entity.user.UserAndRole;
import com.myjz.clansman.entity.user.UserInfo;
import com.myjz.clansman.service.user.UserService;

@Service
public class CustomUserDetailsService implements UserDetailsService{
	
	@Resource
	private UserService userService;
	@Resource
    private PasswordEncoder passwordEncoder;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

		
		System.out.println("校验 登录------");
		/**
         * 1/通过userName 获取到userInfo信息,这里的username 就是帐号
         * 2/通过User(UserDetails)返回details。
         */
        //通过userName获取用户信息
		UserInfo userInfo = userService.queryUserByAccount(username);
		if(userInfo == null) {
			 throw new UsernameNotFoundException("not found");
		}
		//查询权限列表
		List<UserAndRole> roleList = userService.queryUserAndRoleList(userInfo.getUserId());
		//定义权限列表.
        List<GrantedAuthority> authorities = new ArrayList<>();
        // 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
        for(UserAndRole useRoleId : roleList) {
        	GrantedAuthority authoritie = new SimpleGrantedAuthority("ROLE_"+useRoleId.getRoleId());
        	authorities.add(authoritie);
        }
       
        User userDetails = new User(userInfo.getAccount(),passwordEncoder.encode(userInfo.getPassword()),authorities);
        
        return userDetails;
		
	}

}

(2)对WebSecurityConfg 进行配置

@Configuration
@EnableWebSecurity//开启Spring Security的功能
//prePostEnabled属性决定Spring Security在接口前注解是否可用
//@PreAuthorize,@PostAuthorize等注解,设置为true,
//会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfg extends WebSecurityConfigurerAdapter{

	@Autowired
	CustomUserDetailsService userDetailsService;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	 //加载userDetailsService
	 auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
	
	}

}

3.自定义拦截(自定义登录界面,登录登出拦截器等)-form表单提交

(1).对WebSecurityConfg 进行配置

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

/**
 * SpringSecurity安全框架配置
 */
@Configuration
@EnableWebSecurity//开启Spring Security的功能
//prePostEnabled属性决定Spring Security在接口前注解是否可用
//@PreAuthorize,@PostAuthorize等注解,设置为true,
//会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfg extends WebSecurityConfigurerAdapter{

	@Autowired
	CustomUserDetailsService userDetailsService;
	
	org.slf4j.Logger log = LoggerFactory.getLogger(WebSecurityConfg.class);
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//super.configure(auth);
		System.out.println("WebSecurityConfg AuthenticationManagerBuilder");
		auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
	
	}
	
	
	@Autowired 
	LoginSuccessHandler loginSuccessHandler;
	@Autowired 
	LoginFailureHandler loginFailureHandler;
	
	//http请求拦截配置
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		System.out.println("Using configure(HttpSecurity)");
		
		http//配置权限认证
			.authorizeRequests()
			//配置不拦截路由
			.antMatchers("/css/**", "/js/**","/images/**").permitAll()//不拦截资源文件,否则样式,图片等 无法使用
            .antMatchers("/main/500").permitAll()
            .antMatchers("/main/403").permitAll()
            .antMatchers("/main/404").permitAll()
				.anyRequest()//任何请求
				.authenticated();//需要身份验证
				
		http.formLogin()//自定义表单登录
			.loginPage("/login/index").permitAll()//自定义登录界面的url,上面需要不拦截 自定义的url
			.usernameParameter("username")//设置帐号参数,与表单参数一致
			.passwordParameter("password")//设置密码参数,与表单参数一致
			
			// 告诉Spring Security在发送指定路径时处理提交的凭证,默认情况下,将用户重定向回用户来自的页面。登录表单form中action的地址,也就是处理认证请求的路径,
            // 只要保持表单中action和HttpSecurity里配置的loginProcessingUrl一致就可以了,也不用自己去处理,它不会将请求传递给Spring MVC和您的控制器,所以我们就不需要自己再去写一个/login/doLogin的控制器接口了
			.loginProcessingUrl("/login/doLogin")//配置默认登录入口
			.successHandler(loginSuccessHandler)//自定义校验成功处理器
			.failureHandler(loginFailureHandler);//自定义检验失败处理器
		
		//自定义注销
		http.logout()
			.logoutUrl("/login/doLogout")//配置注销入口 (与登录入口一样,只需表单中action 和此处的一致即可)
			.logoutSuccessHandler(new CustomLogoutSuccessHandler());//注销成功处理器
		     
		// 关闭csrf防护 
		//http.csrf().disable();     
	}
		
		
	
	
	/**
     * 指定加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        // 使用BCrypt加密密码
        return new BCryptPasswordEncoder();
    }
	
}

(2).新建 登录成功处理器

import java.io.IOException;
import java.util.Enumeration;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;

/** 
 * @Description 登录成功处理器
 */
@Component("loginSuccessHandler")
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws ServletException, IOException {
		 System.out.println("LoginSuccessHandler");
		 //获得前端传到后端的全部参数
		 Enumeration enu = request.getParameterNames();
         while (enu.hasMoreElements()) {
             String paraName = (String) enu.nextElement(); System.out.println("参数- " + paraName + " : " + request.getParameter(paraName));
         }
         logger.info("登录认证成功");
        
        //重定向跳转路径
         this.getRedirectStrategy().sendRedirect(request, response, "/main/main");
	}
	
	
}

(3). 登录失败处理器

import java.io.IOException;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;


/**
 * @Description 登录失败处理器
 */
@Component("loginFailureHandler")
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {

	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {
		logger.info("登录失败 处理器");
        //以下写登录失败的逻辑
		String errorInfo = "";
        if (exception instanceof BadCredentialsException ||
                exception instanceof UsernameNotFoundException) {
            errorInfo = "账户名或者密码输入错误!";
        } else if (exception instanceof LockedException) {
            errorInfo = "账户被锁定,请联系管理员!";
        } else if (exception instanceof CredentialsExpiredException) {
            errorInfo = "密码过期,请联系管理员!";
        } else if (exception instanceof AccountExpiredException) {
            errorInfo = "账户过期,请联系管理员!";
        } else if (exception instanceof DisabledException) {
            errorInfo = "账户被禁用,请联系管理员!";
        } else {
            errorInfo = "登录失败!";
        }
        logger.info("登录失败原因:" + errorInfo);
		//System.out.println("错误提示:"+exception.getMessage());
		request.getSession().setAttribute("loginError",errorInfo);
		
		
		//表单提交
        this.saveException(request, exception);
        //重定向跳转路径
        this.getRedirectStrategy().sendRedirect(request, response, "/login/index");
	}
	
	
}

(4).注销登录处理器

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

/**
 * 注销登录
 */
@Component
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

	org.slf4j.Logger log = LoggerFactory.getLogger(CustomLogoutSuccessHandler.class);
	
	private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
	
	@Override
	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {
		System.out.println("注销成功!");
		User user = (User) authentication.getPrincipal();
        String username = user.getUsername();
        log.info("username: {}  is offline now", username);
       
        //重定向跳转路径
        redirectStrategy.sendRedirect(request, response, "/login/index");
		
	}


}

(5).登录控制器Controller

@RestController
@RequestMapping("/login")
public class LoginController {

//跳转登录界面
@RequestMapping(value="/index",method=RequestMethod.GET)
	public ModelAndView index(){
		ModelAndView view = new ModelAndView();
		view.setViewName("/login");
		return view;
	}
}

(6). 新建登录界面 login.jsp 以及 控制跳转

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<title>登录页</title>
<myLink>
<link href="${pageContext.request.contextPath}/css/signin.css"
	rel="stylesheet">
</myLink>
</head>
<body>
	<div class="container">
		<c:url value="/login/doLogin" var="loginUrl" />
		<form class="form-signin" method="post" action="${loginUrl}">
			<!-- csrf防护未关闭时,需要添加 -->
			<input type="hidden" name="${_csrf.parameterName}"
				value="${_csrf.token}" />
			<h2 class="form-signin-heading">请登录</h2>

			<label for="inputAccount" class="sr-only">用户名</label> <input
				name="username" type="text" id="inputAccount" class="form-control"
				placeholder="用户名" required autofocus> <label
				for="inputPassword" class="sr-only">密码</label> <input
				name="password" type="password" id="inputPassword"
				class="form-control" placeholder="密码" required>

			<div class="checkbox">
				<label> <input type="checkbox" value="remember-me">
					Remember me
				</label>
			</div>

			<span>${loginError}</span>

			<button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
		</form>
	</div>

</body>
</html>

(7).配置错误页面 403 404 500 适用于 SpringBoot 2.x
只要实现下面的类即可

@Configuration
public class ErrorPageConfig {
	 @Bean
	    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
	        WebServerFactoryCustomizer<ConfigurableWebServerFactory> webCustomizer = new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
	            @Override
	            public void customize(ConfigurableWebServerFactory factory) {
	            	System.out.println("ErrorPageConfig------");
	                ErrorPage[] errorPages = new ErrorPage[] {
	                        new ErrorPage(HttpStatus.FORBIDDEN, "/main/403"),
	                        new ErrorPage(HttpStatus.NOT_FOUND, "/main/404"),
	                        new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/main/500"),
	                };
	                factory.addErrorPages(errorPages);
	            }
	        };
	        return webCustomizer;
	    }
}

很多坑的点和异常 都在代码中有标记 大家要注意 以下特别提醒

  1. http loginPage参数 配置时 一定要加permitAll() 忽略拦截 不然登录界面出不来
  2. http usernameParameter 和passwordParameter 参数 配置时 一定要和界面的name 参数保持一致
  3. http loginProcessingUrl参数 配置 保持表单中action一致,这里还有个点,刚开始我并没有使用jstl 的<c:url> 标签,直接放了action=“/login/doLogin”,其实url标签默认加了访问路径,如果不用<c:url> 标签 ,必须 action=“${pageContext.request.contextPath}/login/doLogin” 才可以,注销登录同理
//自定义配置中注销的配置
		http.logout()
			.logoutUrl("/login/doLogout")//配置注销入口 (与登录入口一样,只需表单中action 和此处的一致即可)
			.logoutSuccessHandler(new CustomLogoutSuccessHandler());//注销成功处理器
		     
//注销登录 的界面写法
<form action="${pageContext.request.contextPath}/login/logout"  method="post" >	
	<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> 
	<button type="submit">退出1</button>
</form>

或者
引入 jstl 标签 
<c:url value="/login/logout" var="logoutUrl"/>
<form action="${logoutUrl}"  method="post" >
	 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> 
	 <button type="submit">退出1</button>
</form>
  1. 关闭csrf防护 如果不关闭防护的话 登录界面中form 表单必须加上 csrf 的参数
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
  1. 界面 的样式 都没生效,F12 看浏览器开发工具,发现报错
“because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled”

问题在于我原来配置中没有将 “/js/" ,"/css/” 的路径添加到配置中,导致没有验证的用户没有权限访问

.antMatchers("/css/**", "/js/**","/images/**").permitAll()

权限校验还未添加 未完待续…

引用并参考以下文章
官方中文文档: https://www.springcloud.cc/spring-reference.html Spring
security API 文档:https://docs.spring.io/spring-security/site/docs/5.0.0.M3/api/
官方中文说明文档:https://www.springcloud.cc/spring-security.html