> 文章列表 > Springsecurity课程笔记06-13章基于数据库的方法授权

Springsecurity课程笔记06-13章基于数据库的方法授权

Springsecurity课程笔记06-13章基于数据库的方法授权

动力节点Springsecurity视频课程

6 密码处理

6.1 为什么要加密?

csdn 密码泄露事件
泄露事件经过:https://www.williamlong.info/archives/2933.html
泄露数据分析:https://blog.csdn.net/crazyhacking/article/details/10443849

6.2加密方案

密码加密一般使用散列函数,又称散列算法,哈希函数,这些函数都是单向函数(从明文到密文,反之不行)
常用的散列算法有MD5和SHA
Spring Security提供多种密码加密方案,基本上都实现了PasswordEncoder接口,官方推荐使用BCryptPasswordEncoder

6.3 BCryptPasswordEncoder类初体验

拷贝springsecurity-04-inmemory工程,重命名为springsecurity-05-password-encode
test/java 下新建包com.powernode.password,在该包下新建测试类PasswordEncoderTest,如下

@Slf4jpublic class PasswordEncoderTest {@Test@DisplayName("测试加密类BCryptPasswordEncoder")void testPassword(){BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();//加密(明文到密文)String encode1 = bCryptPasswordEncoder.encode("123456");_log_.info("encode1:"+encode1);String encode2 = bCryptPasswordEncoder.encode("123456");_log_.info("encode2:"+encode2);String encode3 = bCryptPasswordEncoder.encode("123456");_log_.info("encode3:"+encode3);//匹配方法,判断明文经过加密后是否和密文一样boolean result1 = bCryptPasswordEncoder.matches("123456", encode1);boolean result2 = bCryptPasswordEncoder.matches("123456", encode1);boolean result3 = bCryptPasswordEncoder.matches("123456", encode1);_log_.info(result1+":"+result2+":"+result3);_assertTrue_(result1);_assertTrue_(result2);_assertTrue_(result3);}
} 

查看控制台发现特点是:**相同的字符串加密之后的结果都不一样,但是比较的时候是一样的,因为加了盐(**salt)了。

上面简单看下即可
小提示:
Ø 开发代码时不允许使用main方法测试,而是使用单元测试来测试
Ø 代码中一般不允许使用System.out.println 直接输出,而是使用日志输出
Ø 单元测试尽量使用断言,而不是使用System.out.println输出

6.4 使用加密器并且加密

修改MySecurityUserConfig类中的加密器bean

@Beanpublic PasswordEncoder passwordEncoder(){//使用加密算法对密码进行加密return new BCryptPasswordEncoder();} 

启动程序测试,发现不能正常登录
原因是输入的密码是进行加密了,但是系统中定义的用户密码没有加密
将系统定义的用户密码修改成密文,如下

@Configurationpublic class MySecurityUserConfig {@Beanpublic UserDetailsService userDetailService() {//        使用org.springframework.security.core.userdetails.User类来定义用户//定义两个用户UserDetails user1 = User._builder_().username("eric").password(passwordEncoder().encode("123456")).roles("student").build();UserDetails user2 = User._builder_().username("thomas").password(passwordEncoder().encode("123456")).roles("teacher").build();//创建两个用户InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);return userDetailsManager;}/** 从 Spring5 开始,强制要求密码要加密* @return*/@Beanpublic PasswordEncoder passwordEncoder(){//使用加密算法对密码进行加密return new BCryptPasswordEncoder();}} 

重启程序,再次测试即可,发现登录和访问没问题了

7 查看当前登录用户信息及配置用户权限

复制springsecurity-05-password-encode,复制后为springsecurity-06-loginuser-info

7.1 获取当前登录用户信息

新建一个controller

@RestControllerpublic class CurrentLoginUserInfoController { _/*** 从当前请求对象中获取*/_@GetMapping("/getLoginUserInfo")public Principal getLoginUserInfo(Principal principle){return principle;}_/***从当前请求对象中获取*/_@GetMapping("/getLoginUserInfo1")public Authentication getLoginUserInfo1(Authentication authentication){return authentication;}_/*** 从安全应用上下文(SecurityContextHolder)获取安全应用上下文(SecurityContext),从安全应用上下文中获取认证信息* **@return***/_@GetMapping("/getLoginUserInfo2")public Authentication getLoginUserInfo(){Authentication authentication = SecurityContextHolder._getContext_().getAuthentication();return authentication;}}  

注意Authentication接口继承自 Principal
重启程序,访问

http://localhost:8080/getLoginUserInfo
http://localhost:8080/getLoginUserInfo1
http://localhost:8080/getLoginUserInfo2

运行结果

{
“authorities”: [{
“authority”: “ROLE_teacher”
}],
“details”: {
“remoteAddress”: “0:0:0:0:0:0:0:1”,
“sessionId”: “34E452050095348E6306CF95B2025CD9”
},
“authenticated”: true,
“principal”: {
“password”: null,
“username”: “thomas”,
“authorities”: [{
“authority”: “ROLE_teacher”
}],
“accountNonExpired”: true,
“accountNonLocked”: true,
“credentialsNonExpired”: true,
“enabled”: true
},
“credentials”: null,
“name”: “thomas” }

Principal 定义认证的而用户,如果用户使用用户名和密码方式登录,principal通常就是一个UserDetails(后面再说)
Credentials:登录凭证,一般就是指密码。当用户登录成功之后,登录凭证会被自动擦除,以防泄露。
authorities:用户被授予的权限信息。

7.2 配置用户权限

配置用户权限有两种方式:
配置roles
配置authorities
注意事项:
如果给一个用户同时配置roles和authorities,哪个写在后面哪个起作用
配置roles时,权限名会加上ROLE_。
修改WebSecurityConfig代码中的

    // 注意 1 哪个写在后面哪个起作用 2 角色变成权限后会加一个ROLE_前缀,比如ROLE_teacher//        UserDetails user2 = User.builder()//                .username("thomas")//                .password(passwordEncoder().encode("123456"))//                .authorities("teacher:add","teacher:update")//                .roles("teacher")//                .build();UserDetails user2 = User._builder_().username("thomas")             .password(passwordEncoder().encode("123456")).roles("teacher").authorities("teacher:add","teacher:update").build();  

重启程序使用thomas登录,然后查看用户认证信息

http://localhost:8080/getLoginUserInfo

可以看到authorities的情况。
从设计层面讲,角色和权限是两个完全不同的东西
从代码层面来讲,角色和权限并没有太大区别,特别是在Spring Security中

8 授权(对URL进行授权)

上面讲的实现了认证功能,但是受保护的资源是默认的,默认所有认证(登录)用户均可以访问所有资源,不能根据实际情况进行角色管理,要实现授权功能,需重写WebSecurityConfigureAdapter 中的一个configure方法
复制springsecurity-06-loginuser-info 工程,然后改名为springsecurity-07-url
新建WebSecurityConfig类,重写configure(HttpSecurity http)方法
WebSecurityConfig 完整代码如下:

@Configuration@Slf4jpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//角色student或者teacher都可以访问/student/** 这样的url.mvcMatchers("/student/*").hasAnyRole("student", "teacher")// 角色teacher 可以访问teacher/**.mvcMatchers("/teacher/**").hasRole("teacher")//权限admin:query 可以访问/admin**//                .mvcMatchers("/admin/**").hasAuthority("admin:query")//角色teacher 或者权限admin:query 觉可以访问admin/**.mvcMatchers("/admin/**").access("hasRole('teacher') or hasAuthority('admin:query')")//任何请求均需要认证.anyRequest().authenticated();//使用表单登录http.formLogin();}} 

使用admin登录,访问

http://localhost:8080/teacher/query
http://localhost:8080/student/query
http://localhost:8080/admin/query

分别查看效果,实现权限控制
上面是对URL资源进行控制,就是哪些权限可以访问哪些URL。

9 授权(方法级别的权限控制)

上面学习的认证与授权都是基于URL的,我们也可以通过注解灵活的配置方法安全,我们先通过@EnableGlobalMethodSecurity开启基于注解的安全配置。

9.1 新建模块springsecurity-08-method

9.2 添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency> 

9.3 新建启动类并复制 CurrentLoginUserInfoController类

新建启动类Application,学员自行创建

9.4 新建service及其实现

com.powernode.service 新建教师接口

public interface TeacherService {String add();String update();String delete();String query();} 

com.powernode.service.impl 实现接口

@Service@Slf4jpublic class TeacherServiceImpl implements TeacherService {@Overridepublic String add() {_log_.info("添加教师成功");return "添加教师成功";}@Overridepublic String update() {_log_.info("修改教师成功");return "修改教师成功";}@Overridepublic String delete() {_log_.info("删除教师成功");return "删除教师成功";}@Overridepublic String query() {_log_.info("查询教师成功");return "查询教师成功";}
} 

9.5 修建TeacherController

@RestController@RequestMapping("/teacher")public class TeacherController {@Resourceprivate TeacherService teacherService;@GetMapping("/query")public String queryInfo() {return teacherService.query();}@GetMapping("/add")public String addInfo() {return teacherService.add();}@GetMapping("/update")public String updateInfo() {return teacherService.update();}@GetMapping("/delete")public String deleteInfo() {return teacherService.delete();}
} 

9.6 新建安全配置类

com.powernode.config下新建用户配置类

@Configurationpublic class MySecurityUserConfig {@Beanpublic UserDetailsService userDetailService() {//        使用org.springframework.security.core.userdetails.User类来定义用户//定义两个用户UserDetails user1 = User._builder_().username("eric").password(passwordEncoder().encode("123456")).roles("student").build();UserDetails user2 = User._builder_().username("thomas").password(passwordEncoder().encode("123456")).roles("teacher").build();UserDetails user3 = User._builder_().username("admin").password(passwordEncoder().encode("123456")).authorities("teacher:add", "teacher:update").roles("teacher").build();//创建两个用户InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);return userDetailsManager;}/** 从 Spring5 开始,强制要求密码要加密* @return*/@Beanpublic PasswordEncoder passwordEncoder(){//使用加密算法对密码进行加密return new BCryptPasswordEncoder();}} 

新建WebSecurityConfig类

@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {//任何访问均需要认证http.authorizeRequests().anyRequest().authenticated();http.formLogin(); //使用表单登陆方式}} 

9.7 启动程序并访问

访问以下地址

| http://localhost:8080/teacher/add
http://localhost:8080/teacher/update
http://localhost:8080/teacher/delete

http://localhost:8080/teacher/query

通过admin或thomas登录均可以访问所有资源

9.8 修改安全配置类WebSecurityConfig

加上启用全局方法安全注解

@EnableGlobalMethodSecurity(prePostEnabled = true)

修改后,完整代码如下:

@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {//任何访问均需要认证http.authorizeRequests().anyRequest().authenticated();//使用表单登录http.formLogin();}} 

9.9 修改TeacherServiceImpl

在每个方法上加上前置授权注解:@PreAuthorize
完整代码如下:

@Service@Slf4jpublic class TeacherServiceImpl implements TeacherService {@Override@PreAuthorize("hasAuthority('teacher:add') OR hasRole('teacher')")public String add() {_log_.info("添加教师成功");return "添加教师成功";}@Override@PreAuthorize("hasAuthority('teacher:update')")public String update() {_log_.info("修改教师成功");return "修改教师成功";}@Override@PreAuthorize("hasAuthority('teacher:delete')")public String delete() {_log_.info("删除教师成功");return "删除教师成功";}@Override@PreAuthorize("hasRole('teacher')")public String query() {_log_.info("查询教师成功");return "查询教师成功";}} 

9.10 启动并运行

运行程序分别使用admin和teacher登录,可以查看不同效果
http://localhost:8080/teacher/add
http://localhost:8080/teacher/update
http://localhost:8080/teacher/delete
http://localhost:8080/teacher/query

发现thomas可以访问添加和查询,别的不能访问,amdin可以访问添加和更新,别的不能访问。
代码说明:
Ø EnableGlobalMethodSecurity注解的属性prePostEnabled = true 解锁@PreAuthorize 和@PostAuthorize注解,@PreAuthorize 在方法执行前进行验证,@PostAuthorize 在方法执行后进行验证
Ø EnableGlobalMethodSecurity的securedEnabled = true 解锁@Secured注解,@Secured和@PreAuthorize用法基本一样 @Secured对应的角色必须要有ROLE_前缀

10 SpringSecurity 返回json

前后端分离成为企业应用开发中的主流,前后端分离通过json进行交互,登录成功和失败后不用页面跳转,而是一段json提示

10.1 新建模块springsecurity-09-json

10.2 添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency> 

10.3 新建三个controller和获取登录用户信息的controller

参照1.2.4 创建,可以直接拷贝过来

10.4 新建启动类

com.powernode下新建Application类,学员自行创建

10.5 创建统一响应类HttpResult

在com.powernode.vo中创建该类

@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic class HttpResult {private Integer code;private String msg;private Object data;public HttpResult(Integer code, String msg) {this.code = code;this.msg = msg;}} 

10.6 创建登录成功处理器

com.powernode.config 包下创建

@Componentpublic class MyAutheticationSuccessHandle implements AuthenticationSuccessHandler {@Resourceprivate ObjectMapper objectMapper;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=utf-8");HttpResult httpResult = new HttpResult(200, "登录成功", authentication);String str = objectMapper.writeValueAsString(httpResult);response.getWriter().write(str);response.getWriter().flush();}} 

10.7 创建登录失败处理器

 _/*** 登陆失败的处理器*/_@Component@Slf4jpublic class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@Resourceprivate ObjectMapper objectMapper;_/*** **@param **request 当前的请求对象* **@param **response 当前的响应对象* **@param **exception 失败的原因的异常* **@throws **IOException* **@throws **ServletException*/_@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {System._err_.println("登陆失败");//设置响应编码response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=utf-8");//返回JSON出去HttpResult result=HttpResult.builder()                .code(-1)                .msg("登录失败")                .build();                if(exception instanceof BadCredentialsException){result.setData("密码不正确");}else if(exception instanceof DisabledException){result.setData("账号被禁用");}else if(exception instanceof UsernameNotFoundException){result.setData("用户名不存在");}else if(exception instanceof CredentialsExpiredException){result.setData("密码已过期");}else if(exception instanceof AccountExpiredException){result.setData("账号已过期");}else if(exception instanceof LockedException){result.setData("账号被锁定");}else{result.setData("未知异常");}//把result转成JSONString json = objectMapper.writeValueAsString(result);//响应出去PrintWriter out = response.getWriter();out.write(json);out.flush();}} 

10.8 创键无权限处理器

_/*** 无权限的处理器*/_@Componentpublic class MyAccessDeniedHandler implements AccessDeniedHandler {//声明一个把对象转成JSON的对象@Resourceprivate ObjectMapper objectMapper;@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {//设置响应编码response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=utf-8");//创建响应对象HttpResult result= HttpResult.builder().code(-1).msg("用户没有访问权限").build();        //把result转成JSONString json = objectMapper.writeValueAsString(result);//响应json出去PrintWriter out = response.getWriter();out.write(json);out.flush();}} 

10.9 创建登出(退出)处理器

 _/*** 退出成功的处理器*/_@Componentpublic class MyLogoutSuccessHandler implements LogoutSuccessHandler {//声明一个把对象转成JSON的对象@Resourceprivate ObjectMapper objectMapper;_/**** **@param **request* **@param **response* **@param **authentication 当前退出的用户对象* **@throws **IOException* **@throws **ServletException*/_@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System._out_.println("退出成功");//设置响应编码response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=utf-8");//返回JSON出去HttpResult result= HttpResult.builder()                .code(200)                .msg("退出成功")                .build();//把result转成JSONString json = objectMapper.writeValueAsString(result);//响应出去PrintWriter out = response.getWriter();out.write(json);out.flush();}} 

10.10 创建用户配置类

@Configurationpublic class MySecurityUserConfig {@Beanpublic UserDetailsService userDetailService() {//        使用org.springframework.security.core.userdetails.User类来定义用户//定义用户UserDetails user1 = User._builder_().username("eric").password(passwordEncoder().encode("123456")).roles("student").build();UserDetails user2 = User._builder_().username("thomas").password(passwordEncoder().encode("123456")).roles("teacher").build();UserDetails user3 = User._builder_().username("admin").password(passwordEncoder().encode("123456")).roles("admin").build();//创建用户InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);userDetailsManager.createUser(user3);return userDetailsManager;}/** 从 Spring5 开始,强制要求密码要加密* @return*/@Beanpublic PasswordEncoder passwordEncoder(){//使用加密算法对密码进行加密return new BCryptPasswordEncoder();}} 

10.11 安全配置类WebSecurityConfig


```java
@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {// 注入登陆成功的处理器@Autowiredprivate MyAutheticationSuccessHandle successHandler;// 注入登陆失败的处理器@Autowiredprivate MyAuthenticationFailureHandler failureHandler;// 注入没有权限的处理器@Autowiredprivate MyAccessDeniedHandler accessDeniedHandler;//  注入退出成功的处理器@Autowiredprivate MyLogoutSuccessHandler logoutSuccessHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {//配置拒绝访问处理器        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);//配置登录成功处理器,配置登录失败处理器       http.formLogin().successHandler(successHandler).failureHandler(failureHandler); //配置退出成功处理器http.logout().logoutSuccessHandler(logoutSuccessHandler);http.authorizeRequests().mvcMatchers("/teacher/**").hasRole("teacher").anyRequest().authenticated();}} 

10.12 启动程序

10.13 访问测试

可以使用admin用户实验登录失败、登录成功、退出和访问http://localhost:8080/teacher/query 查看无权访问的效果

11 使用自定义UserDetailsService实现获取用户认证信息

11.1 新建子模块springsecurity-10-userdetailservice

11.2 添加依赖

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency> 

11.3 新建启动类

com.powernode包下新建启动类Application,学员自行创建

11.4 新建三个controller

参照1.2.4 创建,可以直接拷贝过来

11.5 新建获取登录用户认证信息的controller

拷贝7.1 即可

11.6 新建用户信息类

com.powernode.vo包下新建SecurityUser 类,该类实现接口UserDetails接口

public class SecurityUser implements UserDetails {@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {//用户密码使用密文        return new BCryptPasswordEncoder().encode("123456");}@Overridepublic String getUsername() {//定义用户名return "thomas";}@Overridepublic boolean isAccountNonExpired() {//账号是否未过期,返回true 未过期return true;}@Overridepublic boolean isAccountNonLocked() {//账号是否未锁定,返回true 未锁定return true;}@Overridepublic boolean isCredentialsNonExpired() {//凭据(凭证),目前可以理解成密码,是否未过期,返回true 未过期return true;}@Overridepublic boolean isEnabled() {//账号是否可以,返回true可用return true;}} 

代码说明:
用户实体类需要实现UserDetails接口,并实现该接口中的7个方法, UserDetails 接口的7个方法如下图:

11.7 新建类实现UserDetailService接口

com.powernode.service.impl 包下新建UserServiceImpl 实现UserDetailService

@Servicepublic class UserServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SecurityUser securityUser= new SecurityUser();if(username==null &#124;&#124; !username.equals(securityUser.getUsername())){throw new UsernameNotFoundException("该用户不存在");}return securityUser;}} 

11.8 新建安全配置类

com.powernode.config下新建WebSecurityConfig类,配置密码编码器

@Configuration@Slf4jpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}}

启动程序,并使用浏览器访问程序:http://localhost:8080/student/query
发现需要登录,使用thomas/123456 登录后,即可正常访问。
访问:http://localhost:8080/getLoginUserInfo
发现该用户并没有权限信息

11.9 配置用户权限信息

修改SecurityUser类中的getAuthorities 方法

@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {GrantedAuthority g1=()->"student:query"; //使用lambda表达式创建接口实现类,而不是使用匿名内部类来实现接口//       GrantedAuthority g1=new SimpleGrantedAuthority("student:query"); // 使用子类创建对象List<GrantedAuthority> grantedAuthorityList=new ArrayList<>();grantedAuthorityList.add(g1);return grantedAuthorityList;} 

11.10 修改要访问controller中的方法需要哪些权限

修改WebSecurityConfig,添加全局方法拦截注解@EnableGlobalMethodSecurity(prePostEnabled = true)
注意可以去掉:@Configuration注解了

修改StudentController
添加 @PreAuthorize(“hasAuthority(‘student:query’)”) 注解修改后如下:

@RestController@RequestMapping("/student")public class StudentController {@GetMapping("/query")@PreAuthorize("hasAuthority('student:query')")public String queryInfo(HttpServletRequest request){return "I am a student,My name is Eric";}} 

修改TeacherController
添加 @PreAuthorize(“hasAuthority(teacher:query’)”) 注解修改后如下:

@RestController@RequestMapping("/teacher")public class TeacherController {@GetMapping("/query")@PreAuthorize("hasAuthority('teacher:query')")public String queryInfo(){return "I am a teacher,My name is Thomas";}} 

启动测试,使用thomas/123456 登录系统,发现可以访问student/query,不可以访问teacher/query
再次访问:http://localhost:8080/getLoginUserInfo 查看用户信息

11.11 为什么讲这个示例?

是为了使用数据库存储用户角色权限信息做准备,只要从数据库中取出数据存储到实现UserDetails 的接口的类中即可,比如SecurityUser 中即可。

12 基于数据库的认证

12.1 创建数据库security_study和表

创建数据库security_study
导入数据库脚本security_study.sql

12.2 创建模块springsecurity-11-database-authentication

12.3 添加依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.13</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><java.version>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-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build> 

12.4 配置数据源和mybatis

新建配置文件application.yml并配置数据源和mybatis

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/security_study?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: rootmybatis:type-aliases-package: com.powernode.entityconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xml 

12.5 新建各个包

12.6 新建用户实体类

com.powernode.entity 包下新建用户实体类

@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic class SysUser implements Serializable {private Integer userId;private String username;private String password;private String sex;private String address;private Integer enabled;private Integer accountNoExpired;private Integer credentialsNoExpired;private Integer accountNoLocked;} 

12.7 新建用户mapper和映射文件

com.powernode.dao下新建

public interface SysUserDao {_/*** 根据用户名获取用户信息* **@param **username* **@return***/_SysUser getByUserName(@Param("username") String username);}

mapper下新建映射文件SysUserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.dao.SysUserDao"><select id="getByUserName" resultType="sysUser">select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_lockedfrom sys_user where username=#{username}</select></mapper> 

注意select后面不要使用*。

12.8 新建启动类

com.powernode包下新建启动类

@SpringBootApplication@MapperScan("com.powernode.dao")public class Application {public static void main(String[] args) {SpringApplication._run_(Application.class,args);}} 

12.9 单元测试

测试dao

@SpringBootTestclass SysUserDaoTest {@Resourceprivate SysUserDao sysUserDao;@Testvoid getByUserName() {SysUser sysUser = sysUserDao.getByUserName("obama");_assertNotNull_(sysUser);}} 

注意单元测试要测试哪些:dao–service-controller,实体类一般不需要测试

12.10 新建安全用户类

com.powernode.vo包下新建类

public class SecurityUser implements UserDetails {private  final SysUser sysUser;public SecurityUser(SysUser sysUser) {this.sysUser=sysUser;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {String userPassword=this.sysUser.getPassword();//注意清除密码
this.sysUser.setPassword(null);return userPassword;}@Overridepublic String getUsername() {return sysUser.getUsername();}@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired().equals(1);}@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked().equals(1);}@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired().equals(1);}@Overridepublic boolean isEnabled() {return sysUser.getEnabled().equals(1);}} 

12.11新建UserServiceImpl 实现UserDetailService接口

@Servicepublic class UserServiceImpl implements UserDetailsService {@Resourceprivate SysUserDao sysUserDao;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserDao.getByUserName(username);if(null==sysUser){throw new UsernameNotFoundException("账号不存在");}return new SecurityUser(sysUser);}} 

12.12 新建service单元测试

@SpringBootTestclass UserServiceImplTest {@Resourceprivate UserServiceImpl userService;@Testvoid loadUserByUsername() {UserDetails userDetails = userService.loadUserByUsername("obama");_assertNotNull_(userDetails);}} 

12.13 新建两个控制器

@RestController@Slf4j@RequestMapping("/student")public class StudentController {@GetMapping("/query")public String queryInfo(){return "query student";}@GetMapping("/add")public String addInfo(){return "add  student!";}@GetMapping("/update")public String updateInfo(){return "update student";}@GetMapping("/delete")public String deleteInfo(){return "delete  student!";}@GetMapping("/export")public String exportInfo(){return "export  student!";}} 
@RestController@Slf4j@RequestMapping("/teacher")public class TeacherController {@GetMapping("/query")@PreAuthorize("hasAuthority('teacher:query')")public String queryInfo(){return "I am a teacher!";}} 

12.14 新建获取登录用户认证信息的controller

从7.1 中拷贝即可

12.15 新建web安全配置类

@EnableGlobalMethodSecurity(prePostEnabled = true)@Slf4jpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated();http.formLogin();}} 

启动并进行各种测试
使用thomas和obama分别登录测试,发现student/query等能访问(不需要访问权限),teacher/query 不能访问(需要访问权限),原因

http://localhost:8080/getLoginUserInfo


发现用户没有权限,但是/teacher/query 需要访问权限,所以/teacher/query 无法访问

13 基于数据库的方法授权

13.1 新建模块

复制springsecurity-11-database-authentication 改名为springsecurity-12-database-authorization-method
注意这个工程已经有认证功能了。下面咱们看下如何设置用户的权限

13.2 新建菜单(权限)实体类

@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic class SysMenu implements Serializable {private Integer id;private Integer pid;private Integer type;private String name;private String code;}

13.3 新建权限mapper和映射文件

public interface SysMenuDao {List<String> queryPermissionByUserId(@Param("userId") Integer userId);}

映射文件SysMenuMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.dao.SysMenuDao"><select id="queryPermissionByUserId" resultType="string">SELECT distinct sm.`code` FROM `sys_role_user` sru inner join sys_role_menu srmon sru.rid=srm.rid inner join sys_menu sm on srm.mid=sm.id  where sru.uid=#{userId}  and sm.delete_flag=0</select></mapper> 

13.4 权限dao的单元测试

@SpringBootTestclass SysMenuDaoTest {@Resourceprivate SysMenuDao sysMenuDao;@Testvoid queryPermissionByUserId() {List<String> menuList = sysMenuDao.queryPermissionByUserId(1);_assertTrue_(!menuList.isEmpty());}} 

13.5 修改SecurityUser实体类

加入一个属性

private List simpleGrantedAuthorities;

修改方法getAuthorities

@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return simpleGrantedAuthorities;} 

添加一个set方法

public void setSimpleGrantedAuthorities(List<SimpleGrantedAuthority> simpleGrantedAuthorities) {this.simpleGrantedAuthorities = simpleGrantedAuthorities;} 

13.6 修改UserServiceImpl

增加设置权限的步骤,修改后如下:

@Service@Slf4jpublic class UserServiceImpl implements UserDetailsService {@Resourceprivate SysUserDao sysUserDao;@Resourceprivate SysMenuDao sysMenuDao;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserDao.getByUserName(username);if(null==sysUser){throw new UsernameNotFoundException("账号不存在");}List<String> strList=sysMenuDao.queryPermissionByUserId(sysUser.getUserId());//使用stream流来转换// SimpleGrantedAuthority::new 相当于调用构造方法List<SimpleGrantedAuthority> grantedAuthorities=strList.stream().map(SimpleGrantedAuthority::new).collect(_toList_());SecurityUser securityUser = new SecurityUser(sysUser);securityUser.setSimpleGrantedAuthorities(grantedAuthorities);return securityUser;}} 

启动并进行各种测试
使用thomas和obama分别登录测试,发现已经有权限功能了

美味小吃