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状态码】