動力節(jié)點(diǎn)SpringSecurity框架視頻教程-springsecurity+

P18-P26學(xué)習(xí)筆記
10 SpringSecurity 返回json
前后端分離成為企業(yè)應(yīng)用開發(fā)中的主流,前后端分離通過json進(jìn)行交互,登錄成功和失敗后不用頁面跳轉(zhuǎn),而是一段json提示
10.1 新建模塊09-springsecurity-json
10.2 添加依賴
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-security</artifactId>
</dependency>
10.3 新建三個controller和獲取登錄用戶信息的controller
參照1.2.4 創(chuàng)建,可以直接拷貝過來
10.4 新建啟動類
com.powernode下新建Application類,學(xué)員自行創(chuàng)建
10.5 創(chuàng)建統(tǒng)一響應(yīng)類HttpResult
在com.powernode.vo中創(chuàng)建該類
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public 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 創(chuàng)建登錄成功處理器
com.powernode.config 包下創(chuàng)建
@Component
public class MyAutheticationSuccessHandle implements AuthenticationSuccessHandler {
@Resource
????private ObjectMapper objectMapper;
????@Override
????public 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 創(chuàng)建登錄失敗處理器
/**
?* 登陸失敗的處理器
?*/
@Component
public class AppAuthenticationFailureHandler implements AuthenticationFailureHandler {
????@Resource
????private ObjectMapper objectMapper;
????/**
?????* @param request 當(dāng)前的請求對象
?????* @param response 當(dāng)前的響應(yīng)對象
?????* @param exception 失敗的原因的異常
?????* @throws IOException
?????* @throws ServletException
?????*/
????@Override
????public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
????????System.err.println("登陸失敗");
????????//設(shè)置響應(yīng)編碼
????????response.setCharacterEncoding("UTF-8");
????????response.setContentType("application/json;charset=utf-8");
????????//返回JSON出去
????????HttpResult result=new HttpResult(-1,"登陸失敗");
????????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轉(zhuǎn)成JSON
????????String json = objectMapper.writeValueAsString(result);
????????//響應(yīng)出去
????????PrintWriter out = response.getWriter();
????????out.write(json);
????????out.flush();
????}
}
10.8 創(chuàng)鍵無權(quán)限處理器
/**
?* 無權(quán)限的處理器
?*/
@Component
public class AppAccessDeniedHandler implements AccessDeniedHandler {
????//聲明一個把對象轉(zhuǎn)成JSON的對象
@Resource
????private ObjectMapper objectMapper;
????@Override
????public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
????????//設(shè)置響應(yīng)編碼
????????response.setCharacterEncoding("UTF-8");
????????response.setContentType("application/json;charset=utf-8");
????????//返回JSON出去
????????HttpResult result=new HttpResult(-1,"您沒有權(quán)限訪問");
????????//把result轉(zhuǎn)成JSON
????????String json = objectMapper.writeValueAsString(result);
????????//響應(yīng)出去
????????PrintWriter out = response.getWriter();
????????out.write(json);
????????out.flush();
????}
}
10.9 創(chuàng)建登出(退出)處理器
/**
?* 退出成功的處理器
?*/
@Component
public class AppLogoutSuccessHandler implements LogoutSuccessHandler {
????//聲明一個把對象轉(zhuǎn)成JSON的對象
@Resource
????private ObjectMapper objectMapper;
????/**
?????*
?????* @param request
?????* @param response
?????* @param authentication 當(dāng)前退出的用戶對象
?????* @throws IOException
?????* @throws ServletException
?????*/
????@Override
????public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
????????System.out.println("退出成功");
????????//設(shè)置響應(yīng)編碼
????????response.setCharacterEncoding("UTF-8");
????????response.setContentType("application/json;charset=utf-8");
????????//返回JSON出去
????????HttpResult result=new HttpResult(200,"退出成功");
????????//把result轉(zhuǎn)成JSON
????????String json = objectMapper.writeValueAsString(result);
????????//響應(yīng)出去
????????PrintWriter out = response.getWriter();
????????out.write(json);
????????out.flush();
????}
}
10.10 創(chuàng)建用戶配置類
@Configuration
public class MySecurityUserConfig {
????@Bean
????public 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();
????????//創(chuàng)建用戶
????????InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
????????userDetailsManager.createUser(user1);
????????userDetailsManager.createUser(user2);
????????userDetailsManager.createUser(user3);
????????return userDetailsManager;
????}
????/*
?????* 從?Spring5 開始,強(qiáng)制要求密碼要加密
?????* @return
?????*/
????@Bean
????public PasswordEncoder passwordEncoder(){
????????//使用加密算法對密碼進(jìn)行加密
????????return new BCryptPasswordEncoder();
????}
}
10.11 安全配置類WebSecurityConfig
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
????// 注入登陸成功的處理器
????@Autowired
????private AutheticationSuccessHandle successHandler;
????// 注入登陸失敗的處理器
????@Autowired
????private AppAuthenticationFailureHandler failureHandler;
????// 注入沒有權(quán)限的處理器
????@Autowired
????private AppAccessDeniedHandler accessDeniedHandler;
????// ?注入退出成功的處理器
????@Autowired
????private AppLogoutSuccessHandler logoutSuccessHandler;
????@Override
????protected void configure(HttpSecurity http) throws Exception {
????????http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
????????http.formLogin().successHandler(successHandler).failureHandler(failureHandler).permitAll();
????????http.logout().logoutSuccessHandler(logoutSuccessHandler);
????????http.authorizeRequests().mvcMatchers("/teacher/**").hasRole("teacher").anyRequest().authenticated();
????}
}
10.12 啟動程序
10.13 訪問測試
可以使用admin用戶實(shí)驗(yàn)登錄失敗、登錄成功、退出和訪問http://localhost:8080/teacher/query 查看無權(quán)訪問的效果
11 使用UserDetailsService獲取用戶認(rèn)證信息
11.1 新建子模塊10-springsecurity-userdetailservice
11.2 添加依賴
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-security</artifactId>
</dependency>
11.3 新建啟動類
com.powernode包下新建啟動類Application,學(xué)員自行創(chuàng)建
11.4 新建三個controller
參照1.2.4 創(chuàng)建,可以直接拷貝過來
11.5 新建獲取登錄用戶認(rèn)證信息的controller
拷貝7.1 即可
11.6 新建用戶信息類
com.powernode.vo包下新建SecurityUser 類,該類實(shí)現(xiàn)接口UserDetails接口
public class SecurityUser implements UserDetails {
????@Override
????public Collection<? extends GrantedAuthority> getAuthorities() {
????????return null;
????}
????@Override
????public String getPassword() {
????????//明文為123456
????????return "$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq";
????}
????@Override
????public String getUsername() {
????????return "thomas";
????}
????@Override
????public boolean isAccountNonExpired() {
????????return true;
????}
????@Override
????public boolean isAccountNonLocked() {
????????return true;
????}
????@Override
????public boolean isCredentialsNonExpired() {
????????return true;
????}
????@Override
????public boolean isEnabled() {
????????return true;
????}
}
代碼說明:
用戶實(shí)體類需要實(shí)現(xiàn)UserDetails接口,并實(shí)現(xiàn)該接口中的7個方法, UserDetails 接口的7個方法如下圖:

11.7 新建類實(shí)現(xiàn)UserDetailService接口
com.powernode.service.impl 包下新建UserServiceImpl 實(shí)現(xiàn)UserDetailService
@Service
public class UserServiceImpl implements UserDetailsService {
????@Override
????public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
????????SecurityUser securityUser= new SecurityUser();
????????if(username==null || !username.equals(securityUser.getUsername())){
????????????throw new UsernameNotFoundException("該用戶不存在");
????????}
????????return securityUser;
????}
}
11.8 新建安全配置類
com.powernode.config下新建WebSecurityConfig類,配置密碼編碼器
@Configuration
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
????@Bean
????public PasswordEncoder passwordEncoder(){
????????return new BCryptPasswordEncoder();
????}
}
啟動程序,并使用瀏覽器訪問程序:http://localhost:8080/student/query
發(fā)現(xiàn)需要登錄,使用thomas/123456 登錄后,即可正常訪問。
訪問:http://localhost:8080/getLoginUserInfo
發(fā)現(xiàn)該用戶并沒有權(quán)限信息

11.9 配置用戶權(quán)限信息
修改SecurityUser類中的getAuthorities 方法
????@Override
????public Collection<? extends GrantedAuthority> getAuthorities() {
???????GrantedAuthority g1=()->"student:query"; //使用lambda表達(dá)式
// ??????GrantedAuthority g1=new SimpleGrantedAuthority("student:query");
???????List<GrantedAuthority> grantedAuthorityList=new ArrayList<>();
???????grantedAuthorityList.add(g1);
???????return grantedAuthorityList;
????}
11.10 修改要訪問controller中的方法需要哪些權(quán)限
修改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 XXX";
????}
}
修改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 登錄系統(tǒng),發(fā)現(xiàn)可以訪問student/query,不可以訪問teacher/query
再次訪問:http://localhost:8080/getLoginUserInfo?查看用戶信息

11.11 為什么講這個示例?
是為了使用數(shù)據(jù)庫存儲用戶角色權(quán)限信息做準(zhǔn)備,只要從數(shù)據(jù)庫中取出數(shù)據(jù)存儲到實(shí)現(xiàn)UserDetails 的接口的類中即可,比如SecurityUser 中即可。
12 基于數(shù)據(jù)庫的認(rèn)證
12.1 創(chuàng)建數(shù)據(jù)庫security_study和表
創(chuàng)建數(shù)據(jù)庫security_study
導(dǎo)入數(shù)據(jù)庫腳本security_study.sql
12.2 創(chuàng)建模塊11-springsecurity-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 配置數(shù)據(jù)源和mybatis
新建配置文件application.yml并配置數(shù)據(jù)源和mybatis
spring:
??datasource:
????driver-class-name: com.mysql.cj.jdbc.Driver
????url: jdbc:mysql://127.0.0.1:3306/security_study?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
????username: root
????password: root
mybatis:
??type-aliases-package: com.powernode.entity
??configuration:
????map-underscore-to-camel-case: true
????log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
??mapper-locations: classpath:mapper/*.xml
12.5 新建各個包

12.6 新建用戶實(shí)體類
com.powernode.entity 包下新建用戶實(shí)體類
@Data
public 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 {
????/**
?????* 根據(jù)用戶名獲取用戶信息
?????* @param username
?????* @return
?????*/
????SysUser getByUserName(@Param("username") String username);
}
mapper下新建映射文件SysUserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
????????PUBLIC "-//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_locked
????????from 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
@SpringBootTest
class SysUserDaoTest {
????@Resource
????private SysUserDao sysUserDao;
????@Test
????void getByUserName() {
????????SysUser sysUser = sysUserDao.getByUserName("obama");
????????assertNotNull(sysUser);
????}
}
注意單元測試要測試哪些:dao--service-controller,實(shí)體類一般不需要測試
12.10 新建安全用戶類
com.powernode.vo包下新建類
public class SecurityUser implements UserDetails {
????private ?final SysUser sysUser;
????public SecurityUser(SysUser sysUser) {
????????this.sysUser=sysUser;
????}
????@Override
????public Collection<? extends GrantedAuthority> getAuthorities() {
????????return null;
????}
????@Override
????public String getPassword() {
????????String userPassword=this.sysUser.getPassword();
//注意清除密碼
this.sysUser.setPassword(null);
return userPassword;
????}
????@Override
????public String getUsername() {
????????return sysUser.getUsername();
????}
????@Override
????public boolean isAccountNonExpired() {
????????return sysUser.getAccountNoExpired().equals(1);
????}
????@Override
????public boolean isAccountNonLocked() {
????????return sysUser.getAccountNoLocked().equals(1);
????}
????@Override
????public boolean isCredentialsNonExpired() {
????????return sysUser.getCredentialsNoExpired().equals(1);
????}
????@Override
????public boolean isEnabled() {
????????return sysUser.getEnabled().equals(1);
????}
}
12.11新建UserServiceImpl 實(shí)現(xiàn)UserDetailService接口
@Service
public class UserServiceImpl implements UserDetailsService {
????@Resource
????private SysUserDao sysUserDao;
????@Override
????public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
????????SysUser sysUser = sysUserDao.getByUserName(username);
????????if(null==sysUser){
????????????throw new UsernameNotFoundException("賬號不存在");
????????}
????????return new SecurityUser(sysUser);
????}
}
12.12 新建service單元測試
@SpringBootTest
class UserServiceImplTest {
????@Resource
????private UserServiceImpl userService;
????@Test
????void 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 新建獲取登錄用戶認(rèn)證信息的controller
從7.1 中拷貝即可
12.15 新建web安全配置類
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
????@Bean
????public PasswordEncoder passwordEncoder() {
????????return new BCryptPasswordEncoder();
????}
????@Override
????protected void configure(HttpSecurity http) throws Exception {
????????http.authorizeRequests().anyRequest().authenticated();
????????http.formLogin();
????}
}
啟動并進(jìn)行各種測試
使用thomas和obama分別登錄測試,發(fā)現(xiàn)student/query等能訪問,teacher/query 不能訪問,原因
http://localhost:8080/getLoginUserInfo
發(fā)現(xiàn)用戶沒有權(quán)限,但是/teacher/query 需要訪問權(quán)限