JDK:1.8html
MAVEN:3.5前端
SpringBoot:2.0.4java
SpringSecurity:5.0.7mysql
IDEA:2017.02旗艦版git
建立一個SpringBoot項目,引入相關依賴:WEB、JPA、MYSQL、SpringSecurity、lombok、devtoolsweb
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xunyji</groupId> <artifactId>springsecurity03</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springsecurity03</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</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-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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>
在yml配置文件中配置MySQL相關的配置,參考文檔spring
spring: datasource: url: jdbc:mysql://127.0.0.1/testdemo?characterEncoding=utf-8&useSSL=false username: root password: 182838 jpa: properties: hibernate: format_sql: true show_sql: true
該接口主要用於測試用的sql
package com.xunyji.springsecurity03.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 王楊帥 * @create 2018-09-08 21:51 * @desc **/ @RestController @RequestMapping(value = "/test") @Slf4j public class TestController { @GetMapping(value = "/home") public String home() { String info = "尋渝記主頁面"; log.info(info); return info; } }
技巧01:SpringBoot集成了SpringSecurity後,默認會對除了 /login 的全部請求進行攔截認證,因此咱們啓動應用後訪問 /test/home 時會自動重定向到 /login 去進行登陸錄入數據庫
技巧02:SpringSecurity默認提供了一個用戶,用戶名是 user, 用戶密碼在啓動應用時會打印輸出到控制檯apache
技巧03:SpringSecurity默認在登陸成功後會自動跳轉到以前訪問的請求【PS: 這種方式在先後端分離的項目中並不適用,因此須要進行登陸成功和失敗來接,讓登陸成功和失敗後返回JSON信息給前端,具體怎麼跳轉有前端進行控制】
待更新......2018年9月8日22:01:11
用戶表:用於存放用戶的相關信息
角色表:用於存放角色信息【PS: 角色表插入數據時必須所有大寫,並且要以 ROLE_ 開頭】
用戶角色表:用戶存放用戶和角色的關聯信息【PS: 用戶和角色是多對多的關係】
/* Navicat MySQL Data Transfer Source Server : mysql5.4 Source Server Version : 50540 Source Host : localhost:3306 Source Database : testdemo Target Server Type : MYSQL Target Server Version : 50540 File Encoding : 65001 Date: 2018-09-08 21:30:53 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `security_authority` -- ---------------------------- DROP TABLE IF EXISTS `security_authority`; CREATE TABLE `security_authority` ( `id` int(11) NOT NULL AUTO_INCREMENT, `authority` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of security_authority -- ---------------------------- INSERT INTO `security_authority` VALUES ('1', 'ROLE_ADMIN'); INSERT INTO `security_authority` VALUES ('2', 'ROLE_USER'); INSERT INTO `security_authority` VALUES ('3', 'ROLE_BOSS'); -- ---------------------------- -- Table structure for `security_user` -- ---------------------------- DROP TABLE IF EXISTS `security_user`; CREATE TABLE `security_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `email` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of security_user -- ---------------------------- INSERT INTO `security_user` VALUES ('1', 'admin', '$2a$10$AC0nr/qvRHHn2YNsKNbH/.X9f0dHhIZX0457mwFPBJ6jS2/Tcmu3S', '412444321@qq.com'); INSERT INTO `security_user` VALUES ('2', 'wys', '$2a$10$YH9HijmebwcDfTdbx5ho2OlJ6.zewxufvCrnioVGI5PcXFsqNtCd6', '41fasd321@qq.com'); INSERT INTO `security_user` VALUES ('3', 'boss', '$2a$10$iZ/467THEoA7E/MjOA6iJeBpZJpebIfRzvFbZhKNKwyyFfBypmQTi', 'asdfa@qq.com'); -- ---------------------------- -- Table structure for `security_user_role` -- ---------------------------- DROP TABLE IF EXISTS `security_user_role`; CREATE TABLE `security_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `role_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of security_user_role -- ---------------------------- INSERT INTO `security_user_role` VALUES ('1', '1', '1'); INSERT INTO `security_user_role` VALUES ('2', '2', '2'); INSERT INTO `security_user_role` VALUES ('3', '3', '1'); INSERT INTO `security_user_role` VALUES ('4', '3', '2'); INSERT INTO `security_user_role` VALUES ('5', '3', '3'); INSERT INTO `security_user_role` VALUES ('6', '1', '2');
技巧01:利用IDEA批量建立實體類,參考文檔
每一個實體類建立一個Repository
技巧01:利用Repository添加用戶,添加用戶時必須利用PasswordEncoder對密碼進行加密
技巧02:添加用戶前須要建立一個PasswordEncoder的Bean
技巧01:本博文只建立了UserService的服務層,由於僅僅是實現認證和受權的功能
技巧02:若是須要實現自定義的認證邏輯,就必須讓UserService實現UserDetailsService接口,全部認證相關的邏輯都在loadUserByUsername方法中進行
技巧03:loadUserByUsername方法中主要是根據用戶名查找用戶信息以及該用戶的角色信息,該方法的返回值是SpringSecurity提供的User對象
package com.xunyji.springsecurity02.service; import com.xunyji.springsecurity02.pojo.dataobject.SecurityAuthorityDO; import com.xunyji.springsecurity02.pojo.dataobject.SecurityUserDO; import com.xunyji.springsecurity02.pojo.dataobject.SecurityUserRoleDO; import com.xunyji.springsecurity02.repository.AuthorityRepository; import com.xunyji.springsecurity02.repository.UserRepository; import com.xunyji.springsecurity02.repository.UserRoleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; 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.List; import java.util.stream.Collectors; /** * @author 王楊帥 * @create 2018-09-08 20:46 * @desc **/ @Service public class UserService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private UserRoleRepository userRoleRepository; @Autowired private AuthorityRepository authorityRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 根據用戶名查找用戶信息 SecurityUserDO byUsername = userRepository.findByUsername(username); System.out.println(byUsername); // 根據用戶ID查找用戶角色關聯信息 List<SecurityUserRoleDO> byUserId = userRoleRepository.findByUserId(byUsername.getId()); byUserId.stream().forEach(System.out::println); // 獲取該用戶對應的全部角色的ID列表 List<Integer> collect = byUserId.stream() .map(info -> info.getRoleId()) .collect(Collectors.toList()); collect.stream().forEach(System.out::println); // 獲取該用戶對應的全部角色信息 List<SecurityAuthorityDO> allById = authorityRepository.findAllById(collect); allById.stream().forEach(System.out::println); List<SimpleGrantedAuthority> authorityList = allById.stream() .map(info -> new SimpleGrantedAuthority(info.getAuthority())) .collect(Collectors.toList()); authorityList.stream().forEach(System.out::println); // 封裝該用戶的用戶名、密碼、角色 return new User(byUsername.getUsername(), byUsername.getPassword(), authorityList); } }
認證成功後默認是跳轉到以前的請求API,能夠經過認證成功攔截器讓認證成功後返回一些JSON信息
package com.xunyji.springsecurity02.config; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author 王楊帥 * @create 2018-09-08 15:19 * @desc **/ @Component @Slf4j public class XiangXuAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { log.info("登陸成功"); httpServletResponse.setContentType("application/json;charset=UTF-8"); httpServletResponse.getWriter() .write(objectMapper.writeValueAsString(authentication)); } }
認證失敗後默認是跳轉到 /login ,能夠經過認證失敗攔截器讓認證失敗後返回一些JSON信息
package com.xunyji.springsecurity02.config; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author 王楊帥 * @create 2018-09-08 15:22 * @desc **/ @Component @Slf4j public class XiangXuAuthenticationFailureHandler implements AuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { log.info("登錄失敗"); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(exception)); } }
package com.xunyji.springsecurity02.config; import com.xunyji.springsecurity02.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.builders.WebSecurity; 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.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; /** * @author 王楊帥 * @create 2018-09-08 20:13 * @desc **/ @Configuration public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserService userService; @Autowired private AuthenticationFailureHandler xiangXuAuthenticationFailureHandler; @Autowired private AuthenticationSuccessHandler xiangXuAuthenticationSuccessHandler; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()); // 添加自定義的認證邏輯 } @Override public void configure(WebSecurity web) throws Exception { super.configure(web); } @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginProcessingUrl("/furyLogin") // 提交登陸信息的API .usernameParameter("name") // 登陸名 .passwordParameter("pwd") // 登陸密碼 .successHandler(xiangXuAuthenticationSuccessHandler) // 登陸成功處理器 .failureHandler(xiangXuAuthenticationFailureHandler) // 登陸失敗處理器 .and().authorizeRequests() .antMatchers("/test/home").permitAll() // 該api不須要受權 .anyRequest().authenticated() // 剩餘都須要受權 .and() .logout().permitAll() // 登出API不須要受權 .and() .csrf().disable(); } /** * 創建PsswordEncoder對應的Bean * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 建立認證提供者Bean * 技巧01:DaoAuthenticationProvider是SpringSecurity提供的AuthenticationProvider實現類 * @return */ @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); // 建立DaoAuthenticationProvider實例 authProvider.setUserDetailsService(userService); // 將自定義的認證邏輯添加到DaoAuthenticationProvider authProvider.setPasswordEncoder(passwordEncoder); // 設置自定義的密碼加密 return authProvider; } }
package com.xunyji.springsecurity02.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 王楊帥 * @create 2018-09-08 21:17 * @desc **/ @RestController @RequestMapping(value = "/test") @Slf4j @EnableGlobalMethodSecurity(prePostEnabled = true) // 開啓受權 public class TestController { @GetMapping(value = "/home") public String home() { String info = "尋渝記主頁面"; log.info(info); return info; } @PreAuthorize("hasRole('ROLE_BOSS')") // 擁有ROLE_BOSS角色的用戶能夠訪問 @GetMapping(value = "/boss") public String boss() { String info = "擁有ROLE_BOSS權限的才能夠進入"; log.info(info); return info; } @PreAuthorize("hasRole('ROLE_BOSS') OR hasRole('ROLE_ADMIN')") @GetMapping(value = "/admin") public String admin() { String info = "擁有ROLE_ADMIN權限的才能夠進入"; log.info(info); return info; } @PreAuthorize("hasRole('ROLE_BOSS') OR hasRole('ROLE_ADMIN') OR hasRole('ROLE_USER')") @GetMapping(value = "/user") public String user() { String info = "擁有ROLE_USER權限的才能夠進入"; log.info(info); return info; } }
技巧01:若是沒有進行認證前訪問須要進行權限的 Restful 服務,就會自動返回一個登陸頁面【問題:如何返回一個JSON信息呢】
技巧02:登陸認證成功後,若是訪問該用戶權限以外的 Restful 服務,那麼就會提示權限不足【即:403狀態碼】