写在前面: 从2018年底开始学习SpringBoot,也用SpringBoot写过一些项目。现在想对学习Springboot的一些知识总结记录一下。如果你也在学习SpringBoot,可以关注我,一起学习,一起进步。

相关文章:

【Springboot系列】Springboot入门到项目实战


目录

SpringSecurity简介

1、简介

2、Security适配器

SpringSecurity核心类

1、Authentication类

2、SecurityContextHolder类

3、UserDetails类

4、UserDetailsService类

5、GrantedAuthority类

6、DaoAuthenticationProvider类

7、PasswordEncoder类

SpringSecurity的验证机制

1、SpringSecurity的验证机制

搭建项目

1、新建Springboot项目

2、项目结构。

编写代码

1、登录页面

2、登录成功页面

3、Security配置类

4、认证成功处理类LoginSuccessHandle

5、测试控制器

6、测试应用

用户和权限保存在数据库中

1、修改后项目的目录结构。

2、添加依赖

3、配置基本属性。

4、创建用户类和角色权限实体类

5、创建数据访问接口

6、创建自定义服务类Service

7、修改配置类

8、测试应用

9、案例下载


SpringSecurity简介

1、简介

SpringSecurity是一个能够基于Spring的应用程序提供声明式安全保护的安全性框架,它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了SpringIOC(控制反转)和AOP(面向切面编程)功能,为应用系统提供安全访问控制功能,减少了为系统安全控制编写大量重复代码的工作。

官网地址:https://spring.io/projects/spring-security

安全框架主要包含两个操作。

认证:确认用户可以访问当前系统。

授权:确定用户在当前系统中是否能够执行某个操作,即用户所拥有的功能权限。

2、Security适配器

创建一个自定义类继承WebSecurityConfigurerAdapter,并在改类中使用@EnableWebSecurity注解就可以通过重写config方法类配置所需要的安全配置。

WebSecurityConfigurerAdapter是SpringSecurity为Web应用提供的一个适配器,实现了WebSecurityConfigurerAdapter接口,提供了两个方法用于重写开发者需要的安全配置。

protected void configure(HttpSecurity http) throws Exception {
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}

configure(HttpSecurity http)方法中可以通过HTTPSecurity的authorizeRequests()方法定义那些URL需要被保护、那些不需要被保护;通过formLogin()方法定义当前需要用户登录的时候,跳转到的登录页面。

configure(AuthenticationManagerBuilder auth)方法用于创建用户和用户的角色。

用户认证

SpringSecurity是通过configure(AuthenticationManagerBuilder auth)完成用户认证的。使用AuthenticationManagerBuilder的inMemoryAuthentication()方法可以添加用户,并给用户指定权限。

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("aaa").password("{noop}1234").roles("USER");auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("ADMIN", "DBA");
}

上面的代码中添加了两个用户,其中一个用户名是“aaa”,密码是“1234”,用户权限是“USER”;另一个用户名是“admin”,密码是“admin”,用户权限有两个,分别是“ADMIN”和“DBA”。需要注意的是SpringSecurity保存用户权限的时候,会默认使用“ROLE_”,也就是说“USER”实际上是“ROLE_USER”,“ADMIN”实际是“ROLE_ADMIN”,“DBA”实际上是“ROLE_DBA”。

当然,也可以查询数据库获取用户和权限。下面有写到。

用户授权

SpringSecurity是通过configure(HttpSecurity http)完成用户授权的。

HTTPSecurity的authorizeRequests()方法有多个子节点,每个macher按照它们的声明顺序执行,指定用户可以访问的多个URL模式。

  • antMatchers使用Ant风格匹配路径。
  • regexMatchers使用正则表达式匹配路径。

在匹配了请求路径后,可以针对当前用户的信息对请求路径进行安全处理。下表是SpringSecurity提供的安全处理方法。

方法 用途
anyRequest 匹配所有请求路径
access(String) Spring EL 表达式结果为true时可以访问
anonymous() 匿名可以访问
denyAll() 用户不能访问
fullyAuthenticated() 用户完全认证可以访问(非remember-me下自动登录)
hasAnyAuthority(String...) 如果有参数,参数表示权限,则其中任何一个权限可以访问
hasAnyRole(String...) 如果有参数,参数表示角色,则其中任何一个角色可以访问
hasAuthority(String...) 如果有参数,参数表示权限,则其权限可以访问
hasIpAddress(String) 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
hasRole(String) 如果有参数,参数表示角色,则其角色可以访问
permitAll() 用户可以任意访问
rememberMe() 允许通过remember-me登录的用户访问
authenticated() 用户登录后可访问

示例代码如下:

@Override
protected void configure(HttpSecurity http) throws Exception {http.csrf().disable();    //安全器令牌http.authorizeRequests().antMatchers("/login").permitAll().antMatchers("/", "/home").hasRole("USER").antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA").anyRequest().authenticated().and().formLogin().loginPage("/login").usernameParameter("loginName").passwordParameter("password").defaultSuccessUrl("/main").failureUrl("/login?error").and().logout().permitAll().and().exceptionHandling().accessDeniedPage("/accessDenied");
}
  1. http.authorizeRequests() 开始请求权限配置。
  2. antMatchers("/login").permitAll() 请求匹配“/login”,所有用户都可以访问。
  3. antMatchers("/", "/home").hasRole("USER")  请求匹配“/”和“/home”,拥有“ROLE_USER”角色的用户可以访问。
  4. antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA")  请求匹配“/admin/**”,拥有“ROLE_ADMIN”或“ROLE_DBA”角色的用户可以访问。
  5. anyRequest().authenticated()  其余所有的请求都需要认证(用户登录)之后才可以访问。
  6. formLogin()  开始设置登录操作
  7. loginPage("/login")  设置登录页面的访问地址
  8. usernameParameter("loginName").passwordParameter("password")  登录时接收传递的参数“loginName”的值作为用户名,接收传递参数的“password”的值作为密码。如果不设置,默认“username”为用户名,“password”为密码
  9. defaultSuccessUrl("/main")  指定登录成功后转向的页面。
  10. failureUrl("/login?error")  指定登录失败后转向的页面和传递的参数。
  11. logout()   设置注销操作
  12. permitAll()  所有用户都可以访问。
  13. exceptionHandling().accessDeniedPage("/accessDenied")  指定异常处理页面

SpringSecurity核心类

SpringSecurity核心类包括Authentication、SecurityContextHolder、UserDetails、UserDetailsService、GrantedAuthority、DaoAuthenticationProvider和PasswordEncoder。只要掌握了这些SpringSecurity核心类,SpringSecurity就会变得非常简单。

1、Authentication类

Authentication用来表示用户认证信息,用户登录认证之前,SpringSecurity会将相关信息封装为一个Authentication具体实现类的对象,在登录认证成功之后又会生成一个信息更全面、包含用户权限等信息的Authentication对象,然后把它保存在SecurityContextHolder所持有的SecurityContext中,供后续的程序进行调用,如访问权限的鉴定等。

SecurityContextHolder中的getContext()方法

public static SecurityContext getContext() {return strategy.getContext();
}

2、SecurityContextHolder类

SecurityContextHolder是用来保存SecurityContext的。SecurityContext中含有当前所访问系统的用户的详细信息。默认情况下,SecurityContextHolder将使用ThreadLocal来保存SecurityContext,这也就意味着在处于同一线程的方法中,可以从ThreadLocal获取到当前的SecurityContext。

SpringSecurity使用一个Authentication对象来描述当前用户的相关信息。SecurityContextHolder中持有的是当前用户的SecurityContext,而SecurityContext持有的是代表当前用户相关信息的Authentication的引用。这个Authentication对象不需要我们自己创建,在与系统交互的过程中,SpringSecurity会自动创建相应的Authentication对象,然后赋值给当前的SecurityContext。开发过程中常常需要在程序中获取当前用户的相关信息,比如最常见的获取当前登录用户的用户名。

 String username = SecurityContextHolder.getContext().getAuthentication().getName();

获取UserDetails类,该类中包含用户认证相关等信息。

UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

3、UserDetails类

UserDetails是SpringSecurity的一个核心接口。其中定义了一些可以获取用户名,密码、权限等与认证相关的信息的方法。SpringSecurity内部使用UserDetails实现类大都是内置的User类,要使用UserDetails,也可以直接使用该类。在SpringSecurity内部,很多需要使用用户信息的时候,基本上都是使用UserDetails,比如在登录认证的时候。

通常需要在应用中获取当前用户的其他信息,如E-mail、电话等。这时存放在Authentication中的principal只包含认证相关信息的UserDetails对象可能就不能满足我们的要求了。这时可以实现自己的UserDetails,在该实现类中可以定义一些获取用户其他信息的方法,这样将来就可以直接从当前SecurityContext的Authentication的principal中获取这些信息。

UserDetails是通过UserDetailsService的loadUserByUsername()方法进行加载的,UserDetailsService也是一个接口,我们也需要实现自己的UserDetailsService来加载自定义的UserDetails信息。

新建用户表的Service类实现UserDetailsService接口,来重写UserDetailsService的loadUserByUsername()方法,根据用户名查询当前用户信息并返回,返回的类必须继承Security内置的User类。

@Service
public class SysUserService implements UserDetailsService{@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{}
}

4、UserDetailsService类

Authentication.getPrincipal() 的返回类型是Object。

Object getPrincipal();

但很多情况下返回的其实是一个UserDetails的实例。登录认证的时候SpringSecurity会通过UserDetailsService的loadUserByUsername()方法获取对应的UserDetails进行认证,认证通过后会将该UserDetails赋给认证通过的Authentication的Principal,然后在把该Authentication存入SecurityContext。之后如果需要使用用户信息,可以通过SecurityContextHolder获取存放在SecurityContext中的Authentication的principal,转为UserDetails类。

UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

5、GrantedAuthority类

Authentication的getAuthorities()方法可以返回当前Authentication对象用户的所有的权限。

Collection<? extends GrantedAuthority> getAuthorities();

即当前用户拥有的权限。其返回值是一个GrantedAuthority类型的数组,每一个GrantedAuthority对象代表赋予给当前用户的一种权限。GrantedAuthority是一个接口,其通常是通过UserDetailsService进行加载,然后赋予UserDetails的。

GrantedAuthority中只定义了一个getAuthority()方法,该方法返回一个字符串,表示对应的权限,如果对应权限不能用字符串表示,则返回null

获取当前用户的所有权限,存放到List集合中。

UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<String> roleCodes=new ArrayList<>();
for (GrantedAuthority authority : userDetails.getAuthorities()) {roleCodes.add(authority.getAuthority());
}

6、DaoAuthenticationProvider类

SpringSecurity默认了会使用DaoAuthenticationProvider实现AuthenticationProvider接口,专门进行用户认证的处理。DaoAuthenticationProvider在进行认证的时候需要一个UserDetailsService来获取用户的信息UserDetails,其中包括用户名,密码和所拥有的权限等。如果需要改变认证的方式,开发者可以实现自己的AuthenticationProvider。

7、PasswordEncoder类

在SpringSecurity中,对密码的加密都是由PasswordEncoder来完成的。在SpringSecurity中,已经对PasswordEncoder有了很多实现,包括md5加密,SHA-256加密等,开发者只需要直接拿来用就可以。在DaoAuthenticationProvider中,有一个就是PasswordEncoder熟悉,密码加密功能主义靠它来完成。

SpringSecurity的验证机制

1、SpringSecurity的验证机制

SpringSecurity大体上是由一堆Filter实现的,Filter会在SpringMVC前拦截请求。Filter包括登出Filter(LogoutFilter)、用户名密码验证Filter(UsernamePasswordAuthenticationFilter)之类。Filter在交由其他组件完成细分的功能,最常用的UsernamePasswordAuthenticationFilter会持有一个AuthenticationManager引用,AuthenticationManager是一个验证管理器,专门负责验证。但AuthenticationManager本身并不做具体的验证工作,AuthenticationManager持有一个AuthenticationProvider集合,AuthenticationProvider才是做验证工作的组件,验证成功或失败之后调用对应的Handler。

搭建项目

1、新建Springboot项目

创建一个SpringBoot项目,添加SpringSecurity和Thymeleaf依赖。

pom.xml完整代码。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.2.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.mcy</groupId><artifactId>security-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>security-demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

2、项目结构。

编写代码

1、登录页面

用于测试的HTML页面,用了bootstrap的样式文件。

login.html登录页面,主要有一个form表单和账号密码输入框,用于向login请求提交username和password,从而进行登录。Security默认登录地址login,退出地址为logout。代码如下。

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Security登录</title><link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body><div class="panel panel-primary" style="width: 500px; margin: 5% auto;"><div class="panel-heading">登录</div><div class="panel-body"><form action="login" method="post" class="form-horizontal"><!--用户名或密码错误提示--><div th:if="${param.error != null}"><div class="alert alert-danger"><p color="red">用户名或密码错误!</p></div></div><!--注销提示--><div th:if="${param.logout != null}"><div class="alert alert-success"><p color="red">注销成功!</p></div></div>//sec:authorize可以判断用户是否登录,用户权限,设置该菜单是否显示<p sec:authorize="isAnonymous()">未登录显示</p><p sec:authorize="isAuthenticated()">登录显示</p><div class="input-group input-sm"><label class="input-group-addon"><i class="glyphicon glyphicon-user"></i></label><input type="text" class="form-control" name="username" placeholder="请输入用户名"></div><div class="input-group input-sm"><label class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></label><input type="password" class="form-control" name="password" placeholder="请输入密码"></div><div class="form-actions"><input type="submit" class="btn btn-block btn-primary btn-default" value="登录"></div></form></div></div>
</body>
</html>

用于用户名或密码错误提示。

<div th:if="${param.error != null}"><div class="alert alert-danger"><p color="red">用户名或密码错误!</p></div>
</div>

退出,注销提示。

<div th:if="${param.logout != null}"><div class="alert alert-success"><p color="red">注销成功!</p></div>
</div>

注意这些提示都是SpringSecurity里边自带的。

sec:authorize用于判断用户是否登录,用户是否拥有哪些角色权限,一般在前台页面控制菜单是否显示。

2、登录成功页面

home.html是ROLE_USER用户登录之后显示的页面,同时提供了一个超链接到admin页面,代码如下。

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Home页面</title><link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">Home页面</h3></div></div><h3><p>欢迎[<span color="red" th:text="${user}">用户名</span>]访问Home页面!您的权限是:<span color="red" th:text="${role}">权限</span></p><p><a href="admin">访问admin页面</a></p><p><a href="logout">安全退出</a></p></h3>
</body>
</html>

admin.html是ROLE_ADMIN用户登录之后显示的页面,同时提供了一个到dba页面的超链接,代码如下。

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Admin页面</title><link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">Admin页面</h3></div></div><h3><p>欢迎[<span color="red" th:text="${user}">用户名</span>]访问Admin页面!您的权限是:<span color="red" th:text="${role}">权限</span></p><p><a href="dba">访问dba页面</a></p><p><a href="logout">安全退出</a></p></h3>
</body>
</html>

dba.html页面只是显示简单的欢迎语句,代码如下。

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>dba页面</title><link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">dba页面</h3></div></div><h3><p>欢迎[<span color="red" th:text="${user}">用户名</span>]访问dba页面!您的权限是:<span color="red" th:text="${role}">权限</span></p><p><a href="logout">安全退出</a></p><p sec:authorize="isAnonymous()">未登录显示</p><p sec:authorize="isAuthenticated()">登录显示</p><p sec:authorize="hasRole('ROLE_ADMIN')">权限包含ROLE_ADMIN显示</p><p sec:authorize="!hasRole('ROLE_ADMIN')">权限不包含ROLE_ADMIN登录显示</p></h3>
</body>
</html>

accessDenied.html是访问拒绝页面,如果登录的用户没有权限访问该页面,会进行提示,代码如下。

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>访问拒绝页面</title><link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">AccessDenied页面</h3></div></div><h3><p>欢迎[<span color="red" th:text="${user}">用户名</span>],您没有权限访问页面!您的权限是:<span color="red" th:text="${role}">权限</span></p><p><a href="logout">安全退出</a></p></h3>
</body>
</html>

3、Security配置类

在项目中新建一个security包,在该包下新建一个WebSecurityConfig类,继承WebSecurityConfigurerAdapter类,用于处理SpringSecurity的用户认证和授权操作,设置页面访问权限。

configure(AuthenticationManagerBuilder auth)和configure(HttpSecurity http)两个方法中分别打印了一句话,用于启动项目时的跟踪调试。

successHandler(new LoginSuccessHandle())用于处理登出成功之后的操作,LoginSuccessHandle类用于处理不同用户跳转到不同页面。

具体代码如下。

import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 用户认证操作* @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {System.out.println("WebSecurityConfig   configure(AuthenticationManagerBuilder auth) 方法被调用。。。。。。");//添加用户,并给予权限auth.inMemoryAuthentication().withUser("aaa").password("{noop}1234").roles("USER");auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("ADMIN", "DBA");}/*** 用户授权操作* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {System.out.println("WebSecurityConfig   configure(HttpSecurity http) 方法被调用。。。。。。");http.csrf().disable();    //安全器令牌http.authorizeRequests()    //开始请求权限配置。.antMatchers("/login", "/static/**").permitAll().antMatchers("/", "/home").hasRole("USER").antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA").anyRequest().authenticated()   //其余所有的请求都需要认证(用户登录)之后才可以访问。.and().formLogin()    //开始设置登录操作.loginPage("/login")   //设置登录页面的访问地址//.defaultSuccessUrl("/main")  //指定登录成功后转向的页面。.successHandler(new LoginSuccessHandle())    //登录成功跳转,LoginSuccessHandle处理不同权限跳转不同页面.failureUrl("/login?error")    //指定登录失败后转向的页面和传递的参数。.and().logout().permitAll()       //退出.and().exceptionHandling().accessDeniedPage("/accessDenied"); //指定异常处理页面}
}

4、认证成功处理类LoginSuccessHandle

认证成功处理类LoginSuccessHandle,实现了AuthenticationSuccessHandler接口,是Spring用来处理用户认证授权并跳转到指定URL的。

重新onAuthenticationSuccess方法,获取当前用户的权限,根据权限跳转到指定的URL路径,代码如下。

import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import java.io.IOException;
import java.util.Set;public class LoginSuccessHandle  implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException {//authentication.getAuthorities() 获取当前用户的权限Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());//获取到登陆者的权限,然后做跳转if (roles.contains("ROLE_ADMIN")){response.sendRedirect("/admin");return;}else if (roles.contains("ROLE_USER")){response.sendRedirect("/home");return;}else {response.sendRedirect("/accessDenied");}}
}

5、测试控制器

新建一个IndexController控制器,提供响应login,home,admin,dba,AccessDenied请求的方法。每个方法通过getUsername()方法获得当前认证用户的用户名,通过getAuthorith()方法获取当前认证用户的权限,并设置到ModelMap当中。

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.ArrayList;
import java.util.List;@Controller
public class IndexController {@RequestMapping(value = {"/", "/login"}, method = RequestMethod.GET)public String index(){return "login";}@RequestMapping("/home")public String homePage(ModelMap map){map.put("user", getUsername());map.put("role", getAuthority());return "home";}@RequestMapping("/admin")public String admin(ModelMap map){map.put("user", getUsername());map.put("role", getAuthority());return "admin";}@RequestMapping("/dba")public String dba(ModelMap map){map.put("user", getUsername());map.put("role", getAuthority());return "dba";}@RequestMapping("/accessDenied")public String accessDenied(ModelMap map){map.put("user", getUsername());map.put("role", getAuthority());return "accessDenied";}/*** 获取当前用户名称* @return*/private String getUsername(){//获取当前用户名称String username = SecurityContextHolder.getContext().getAuthentication().getName();System.out.println("username="+username);return username;}private String getAuthority(){//获得Authentication对象,表示用户认证信息Authentication authentication = SecurityContextHolder.getContext().getAuthentication();List<String> roles = new ArrayList<>();//将角色名称添加到List集合for(GrantedAuthority a: authentication.getAuthorities()){roles.add(a.getAuthority());}System.out.println("role="+roles);return roles.toString();}
}

6、测试应用

运行项目可以看到WebSecurityConfig类中重写的两个方法已经执行,说明自定义的用户认证和用户授权工作已经生效。

在浏览器中输入URL测试应用:

http://localhost:8080 “/”、“/login”、“/home”、“admin”等任何一个当前项目的请求都会被重定向到http://localhost:8080/login页面,因为没有登录,,用户没有访问权限。login页面中,用sec:authorize写了两个标签一个是登录成功显示,一个是未登录显示,没有登录,就只显示了一个。sec:authorize一般用于控制菜单是否显示,效果如图。

输入错误的账号密码,会有提示用户名或密码错误。如图。

输入用户名:aaa,密码:1234,登录,该用户是“ROLE_USER”,跳转到home页面,如图。

单击超链接“访问admin页面”,由于当前用户的权限只是“ROLE_USER”,不能访问admin页面,所以会跳转到访问拒绝页面,如图。

单击超链接“安全退出”,会退出到登录页面,登录页面提示“注销成功!”,如图。

输入用户名:admin,密码admin,登录,该用户是“ROLE_USER”和“ROLE_DBA”权限,跳转到admin页面,如图。

单击超链接“访问dba页面”,由于当前用户的权限是“ROLE_ADMIN”和“ROLE_DBA”,可以访问dba页面,所以会跳转到dba页面,如图。在dba页面代码中,写了四行用sec:authorize判断是否显示的标签,两行判断是否登录,两行判断该用户是否有这个角色,条件成立才显示标签内容,效果如图。

通过测试可以看到,项目已经使用SpringSecurity实现了用户认证和用户授权。

上面的案例并没有把用户和权限存放到数据中,在实际开发中,用户和权限肯定是存放在数据库中,下面就来看看如何把数据存放到数据库中。

用户和权限保存在数据库中

1、修改后项目的目录结构。

2、添加依赖

在原有的项目中,修改pom.xml文件,添加MySQL和JPA依赖。

这里使用的是JPA操作数据库,对JPA不了解的可以访问Spring-Data-Jpa入门

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>

3、配置基本属性。

在src/main/resources下找到application.properties文件,在该配置文件中配置数据源和jpa相关的属性,需要在数据库中先新建一个security数据库。

#数据源信息配置
#数据库地址,jpa数据库名,需要在数据库中先建一个jpa数据库
spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8
#用户名
spring.datasource.username=root
#密码
spring.datasource.password=root
#链接数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#指定数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#配置在日志中显示SQL语句
spring.jpa.show-sql=true
#指定自动创建|更新|验证数据库表结构等配置,配置成updata
#表示如果数据库中存在持久化类对应的表就不创建,不存在就创建对应的表
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.use-new-id-generator-mappings=false

4、创建用户类和角色权限实体类

SysUser权限类,代码如下。

import javax.persistence.*;@Entity
@Table(name = "sys_role")
public class SysRole {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;     //主键private String name;   //权限名称public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

SysUser用户类

import javax.persistence.*;
import java.util.List;@Entity
@Table(name = "sys_user")
public class SysUser {private Integer id;private String name;private String username;private String password;private List<SysRole> roles;@Id@GeneratedValue(strategy = GenerationType.IDENTITY)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@ManyToMany(cascade=CascadeType.REFRESH,fetch=FetchType.EAGER)@JoinTable(name="sys_user_role",joinColumns=@JoinColumn(name="user_id"),inverseJoinColumns=@JoinColumn(name="role_id"))public List<SysRole> getRoles() {return roles;}public void setRoles(List<SysRole> roles) {this.roles = roles;}
}

SysUser类用来保存用户数据,其中username表示用户名,password表示密码,name表示用户姓名,roles表示用户权限的List集合,用户和权限的关系是多对多关系。

5、创建数据访问接口

在项目中新建一个repository包,在该包下新建一个UserRepository接口和RoleRepository接口,继承JpaRepository接口,UserRepository接口中写一个根据用户名去查询的方法findByUsername,遵循Spring-Data-Jpa命名规范,代码如下。

import com.mcy.securitydemo.entity.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;public interface UserRepository extends JpaRepository<SysUser, Integer> {//根据登录用户名查询用户SysUser findByUsername(String username);
}
import com.mcy.securitydemo.entity.SysRole;
import org.springframework.data.jpa.repository.JpaRepository;public interface RoleRepository extends JpaRepository<SysRole, Integer> {}

6、创建自定义服务类Service

在项目中新建一个service包,在该包下新建一个UserService类和RoleService类,UserService类实现了UserDetailsService接口,登录认证的时候SpringSecurity会通过UserDetailsService的loadUserByUsername()方法获取对应的UserDetails进行认证。

UserService类重写了UserDetailsService接口中的loadUserByUsername()方法,在方法中调用持久层接口的findByUsername方法通过JPA进行数据库验证,传递的参数是页面接收到的username。最后将获得的用户名,密码和权限保存到org.springframework.security.core.userdetails.User类中并返回,该User类是SpringSecurity内部的实现,专门用于保存用户名,密码,权限等与认证相关的信息。

UserService类代码

import com.mcy.securitydemo.entity.SysRole;
import com.mcy.securitydemo.entity.SysUser;
import com.mcy.securitydemo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Service;
import java.util.ArrayList;
import java.util.List;/*** 需要实现UserDetailsService接口* 因为在SpringSecurity中配置的相关参数需要是UserDetailsService类的数据*/
@Service
public class UserService implements UserDetailsService {//注入持久层接口UserRepository@Autowiredprivate UserRepository userRepository;/*** 重写UserDetailsService接口中的loadUserByUsername方法,通过该方法查询对应的用户* 返回对象UserDetails是SpringSecurity的一个核心接口。* 其中定义了一些可以获取用户名,密码,权限等与认证相关信息的方法。* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//调用持久层接口findByUsername方法查询用户。SysUser user = userRepository.findByUsername(username);if(user == null){throw new UsernameNotFoundException("用户名不存在");}//创建List集合,用来保存用户权限,GrantedAuthority对象代表赋予当前用户的权限List<GrantedAuthority> authorities = new ArrayList<>();//获得当前用户权限集合List<SysRole> roles = user.getRoles();for(SysRole role: roles){//将关联对象role的name属性保存为用户的认证权限authorities.add(new SimpleGrantedAuthority(role.getName()));}//此处返回的是org.springframework.security.core.userdetails.User类,该类是SpringSecurity内部的实现//org.springframework.security.core.userdetails.User类实现了UserDetails接口return new User(user.getUsername(), user.getPassword(), authorities);}//保存方法public void save(SysUser user) {userRepository.save(user);}
}

RoleService类代码

import com.mcy.securitydemo.entity.SysRole;
import com.mcy.securitydemo.repository.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class RoleService {@Autowiredprivate RoleRepository roleRepository;public void save(SysRole role) {roleRepository.save(role);}
}

7、修改配置类

修改WebSecurityConfig类设置认证方式,修改后的WebSecurityConfig代码如下。

import com.mcy.securitydemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;/*** 用户认证操作* @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {System.out.println("WebSecurityConfig   configure(AuthenticationManagerBuilder auth) 方法被调用。。。。。。");//添加用户,并给予权限auth.inMemoryAuthentication().withUser("aaa").password("{noop}1234").roles("USER");auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("ADMIN", "DBA");//设置认证方式auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());}/*** 用户授权操作* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {System.out.println("WebSecurityConfig   configure(HttpSecurity http) 方法被调用。。。。。。");http.csrf().disable();    //安全器令牌http.authorizeRequests()    //开始请求权限配置。.antMatchers("/login", "/static/**").permitAll().antMatchers("/", "/home").hasRole("USER").antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA").anyRequest().authenticated()   //其余所有的请求都需要认证(用户登录)之后才可以访问。.and().formLogin()    //开始设置登录操作.loginPage("/login")   //设置登录页面的访问地址//.defaultSuccessUrl("/main")  //指定登录成功后转向的页面。.successHandler(new LoginSuccessHandle())    //登录成功跳转,LoginSuccessHandle处理不同权限跳转不同页面.failureUrl("/login?error")    //指定登录失败后转向的页面和传递的参数。.and().logout().permitAll()       //退出.and().exceptionHandling().accessDeniedPage("/accessDenied"); //指定异常处理页面}
}

8、测试应用

打开MySQL数据库,新建security数据库。运行项目根据对象之间关系,JPA会在数据库中自动创建sys_user, sys_role和中间表sys_user_role三张表。

test类位置

因为数据库中没有数据,可以通过test类添加测试数据,添加了三个权限和两个用户。在test类中运行如下代码。

@Autowired
private RoleService roleService;
@Autowired
private UserService userService;@Test
void contextLoads() {
SysRole role = new SysRole();
role.setName("ROLE_USER");
roleService.save(role);
SysRole role1 = new SysRole();
role1.setName("ROLE_ADMIN");
roleService.save(role1);
SysRole role2 = new SysRole();
role2.setName("ROLE_DBA");
roleService.save(role2);SysUser user = new SysUser();
user.setName("张三");
//用于对密码加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode("123456"));
user.setUsername("123456");
List<SysRole> roles = new ArrayList<>();
roles.add(role);    //给123456用户添加ROLE_USER权限
user.setRoles(roles);
userService.save(user);SysUser user1 = new SysUser();
user1.setName("李四");
//用于对密码加密
user1.setPassword(encoder.encode("123456"));
user1.setUsername("system");
List<SysRole> roles1 = new ArrayList<>();
roles1.add(role1);    //给system用户添加ROLE_USER权限
roles1.add(role2);
user1.setRoles(roles1);
userService.save(user1);

运行后数据库中数据如图(密码是加密过后的密码)

在浏览器中输入http://localhost:8080/login访问登录页面,登录用户名为123456, 密码为123456的用户。登录成功页面如图。

和上面aaa用户权限一样,所以能访问的页面也和aaa用户一样。system用户所添加的权限和上面的admin用户权限一样,大家还可以自行添加权限和用户进行测试。

9、案例下载

案例Demo下载地址:https://github.com/machaoyin/security-demo

最后有什么不足之处,欢迎大家指出,期待与你的交流。

SpringSecurity安全控件使用指南相关推荐

  1. Microsoft ScriptControl 控件使用指南

    一,前言 很多知识用到时总是不太把握,往往都得要上网找资料查一番. 将一些实用的资料收藏在这里,作备忘之用.本贴随时会更新. 部分资料来源于网络,感谢分享者,如有雷同,绝无恶意,皆为学习. ----- ...

  2. QT-自定义控件使用指南(飞扬青云)

    本片文章参考 :Qt UI界面美化教程1:["飞扬青云" Qt精美控件]使用教程1_超级大洋葱的博客-CSDN博客_qt界面美化 以此文记录本人 使用遇到的一些坑,特此感谢 飞扬青 ...

  3. 多比图形控件开发指南(二)

    1.3典型案例 以下是多比矢量图控件应用的几个例子,包括电力保障系统.通过 这些例子,你可以知道多比图形控件可以应用于哪些场景. 电力系统案例 电力系统案例 工作流设计器 工作流设计器 化工系统案例 ...

  4. 小程序外卖地图编辑控件实践指南

    先放个图看下地图编辑实际效果: 地图控件示例代码 在开发者工具中预览效果 <!-- map.wxml --> <map id="map" longitude=&q ...

  5. DuiVision控件开发指南

    概述 DuiVision是参考了仿PC管家程序.金山界面库.DuiEngine.DuiLib等多个基于DirectUI的界面库开发的. DirectUI技术一般是指将所有的界面控件都绘制在一个窗口上, ...

  6. WPF界面开发第三方控件入门指南——菜单项

    下载DevExpress v20.2完整版 DevExpress技术交流群3:700924826      欢迎一起进群讨论 DevExpress WPF Subscription拥有120+个控件和 ...

  7. CListCtrl控件使用指南(大全)

    ---------------------------------------------------------------------------------------------------- ...

  8. Plupload 上传控件使用指南

    本文转载至(感谢原作者分享):http://www.cnblogs.com/2050/p/3913184.html#plupload_doc2 我之前写过一篇文章<文件上传利器SWFUpload ...

  9. 银联手机支付控件官方使用指南(ios版)

    目录 版本信息... 2 目录      3 1       概述... 1 2       支付流程介绍... 1 3       测试帐号... 2 4       iOS客户端... 3 4.1 ...

最新文章

  1. iphone开蓝牙wifi上网慢_桌面运维:WiFi信号强,网速却很慢?这样操作就能搞定!...
  2. matlab保存变量的值,怎么不能保存之前的变量值?求解
  3. 在Java SE中使用Hibernate Bean Validator
  4. python列表乘数值_《利用Python进行数据分析》十一章· 时间序列·学习笔记(一)...
  5. 勇探计算机城堡教学反思,神秘的城堡教学反思
  6. CentOS 7.2下编译安装PHP 7
  7. [Python] L1-041 寻找250-PAT团体程序设计天梯赛GPLT
  8. hadoo分布式安装
  9. linux网络子系统分析(四)—— INET连接建立API分析之connect/accept
  10. oracle循环语句小结
  11. 教你怎么用爬虫程序采集企业信息及电话邮箱等信息(以企查查为例)
  12. matlab如何使用random函数,random函数
  13. 初中学校计算机机房管理制度,学校机房管理制度
  14. 免费历史文献数字资源
  15. NativeActivity
  16. 计算机小键盘如何启用,教你电脑开机时小键盘自动启用方法
  17. 约翰斯·霍普金斯大学全球新冠疫情统计数据
  18. Word排版:页眉不能对应每章标题、页脚偶数页消失等问题
  19. S7 A7 K7 V7区别
  20. 云计算到底是什么?我这样看待云计算

热门文章

  1. 级联H桥statcom,级联H桥SVG,级联H桥静止同步补偿器,级联H桥静止无功发生器
  2. 项目管理手记 28 ERP项目的高层支持要知行合一
  3. 3行代码5秒抠图的AI神器,PS什么的靠边了
  4. ignore在MySQL中是什么意思
  5. 萌新小白对ctf理解
  6. 【活动预告】金融大数据治理实践分享(12/03)
  7. html5 2019新年祝福页面,新年祝福语2019
  8. docker容器使用/bin/bash命令
  9. 将组成字符串的所有 非英文字母的字符删除
  10. 中国人民大学与加拿大女王大学金融硕士——在职读研该如何平衡学习与工作呢