Spring Security是Spring官方推荐的认证、授权框架,功能相比Apache Shiro功能更丰富也更强大,但是使用起来更麻烦。
如果使用过Apache Shiro,学习Spring Security会比较简单一点,两种框架有很多相似的地方。
目录
一、准备工作
创建springboot项目
pom.xml
application.yml
二、创建相关的类
UserDetailsService
SecurityConfig.java
SystemProperties.java
MybatisPlusConfig.java
三、完成登录接口
创建数据库实体类
创建持久层接口
创建登录DTO对象
创建控制器类
创建业务层类
自定义登录成功处理器
一、准备工作
创建springboot项目
首先,通过IntelliJ IDEA创建一个springboot项目,项目名为springboot-springsecurity,在pom.xml中添加相关依赖。
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.3.4.RELEASE</version><relativePath /></parent><groupId>com.example</groupId><artifactId>springboot-springsecurity</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>1.8</java.version><jjwt.version>0.9.1</jjwt.version><mysql.version>8.0.28</mysql.version><druid.version>1.1.21</druid.version><lombok.version>1.18.22</lombok.version><mybatis.version>2.2.2</mybatis.version><mybatis-plus.version>3.5.1</mybatis-plus.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--validation--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--spring security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid.version}</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency><!--mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis.version}</version></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!--jjwt--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jjwt.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
application.yml
server:port: 8080servlet:context-path: /spring:datasource:username: rootpassword: rooturl: jdbc:mysql://localhost:3306/spring_securitydriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcemybatis-plus:mapper-locations: classpath:mapper/*Mapper.xmllogging:level:springfox: errorcom.example.security: debugsystem:login-page: /login.htmllogin-url: /user/loginindex-page: /index.htmllogout-url: /user/logoutparameter:username: usernamepassword: passwordwhite-url:- /js/**- /css/**- /images/**- /user/login- /login.html
二、创建相关的类
UserDetailsService
UserDetailsService接口是Spring Security中非常重要的接口,在登录认证的时候会通过这个接口的loadUserByUsername()方法获取用户的信息,来完成登录的用户名、密码校验,完成登录流程。
我们需要创建一个UserDetailsService的实现类,并声明为Spring组件。
package com.example.security.security;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.security.entity.User;
import com.example.security.exception.GlobalException;
import com.example.security.mapper.UserMapper;
import com.example.security.restful.ResponseCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
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.Component;import java.util.ArrayList;
import java.util.List;/*** @author heyunlin* @version 1.0*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {private final UserMapper userMapper;@Autowiredpublic UserDetailsServiceImpl(UserMapper userMapper) {this.userMapper = userMapper;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询用户信息User user = selectByUsername(username);if (user == null) {throw new BadCredentialsException("登录失败,用户名不存在!");} else {List<String> permissions = selectPermissions(username);return org.springframework.security.core.userdetails.User.builder().username(user.getUsername()).password(user.getPassword()).accountExpired(false).accountLocked(false).disabled(!user.getEnable()).credentialsExpired(false).authorities(permissions.toArray(new String[] {})).build();}}/*** 通过用户名查询用户信息* @param username 用户名* @return User*/private User selectByUsername(String username) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("username", username);List<User> list = userMapper.selectList(wrapper);if (list.size() == 1) {return list.get(0);}return null;}/*** 通过用户名查询用户权限* @param username 用户名* @return List<String>*/private List<String> selectPermissions(String username) {if (username == null) {throw new GlobalException(ResponseCode.BAD_REQUEST, "用户名不能为空");}List<String> permissions = new ArrayList<>();permissions.add("/user/login");permissions.add("/user/logout");permissions.add("/user/selectById");return permissions;}}
SecurityConfig.java
创建security的配置类
package com.example.security.config;import com.example.security.security.LoginFailHandler;
import com.example.security.security.LoginSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;/*** @author heyunlin* @version 1.0*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {private final SystemProperties systemProperties;@Autowiredpublic SecurityConfig(SystemProperties systemProperties) {this.systemProperties = systemProperties;}@Beanpublic PasswordEncoder passwordEncoder() {return new PasswordEncoder() {@Overridepublic String encode(CharSequence charSequence) {return (String) charSequence;}@Overridepublic boolean matches(CharSequence charSequence, String s) {return charSequence.equals(s);}};}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 禁用防跨域攻击http.csrf().disable();// 配置各请求路径的认证与授权http.formLogin().loginPage(systemProperties.getLoginPage()) // 自定义登录页面的地址.loginProcessingUrl(systemProperties.getLoginUrl()) // 处理登录的接口地址.usernameParameter(systemProperties.getParameter().get("username")) // 用户名的参数名.passwordParameter(systemProperties.getParameter().get("password")) // 密码的参数名.successHandler(new LoginSuccessHandler(systemProperties))//.successForwardUrl("/index.html") // 登录成功跳转的地址.failureHandler(new LoginFailHandler()); // 登录失败的处理器// 退出登录相关配置http.logout().logoutUrl(systemProperties.getLogoutUrl()) // 退出登录的接口地址.logoutSuccessUrl(systemProperties.getLoginUrl()); // 退出登录成功跳转的地址// 配置认证规则String[] toArray = systemProperties.getWhiteUrl().toArray(new String[]{});http.authorizeRequests().antMatchers(toArray).permitAll() // 白名单,也就是不需要登录也能访问的资源.anyRequest().authenticated();}}
SystemProperties.java
package com.example.security.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Map;/*** @author heyunlin* @version 1.0*/
@Data
@Component
@ConfigurationProperties(prefix = "system")
public class SystemProperties {/*** 登录页面*/private String loginPage;/*** 登录的请求地址*/private String loginUrl;/*** 登录成功后跳转的页面*/private String indexPage;/*** 退出登录的请求地址*/private String logoutUrl;/*** 白名单*/private List<String> whiteUrl;/*** 登录的参数*/private Map<String, String> parameter;
}
MybatisPlusConfig.java
package com.example.security.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author heyunlin* @version 1.0*/
@Configuration
@MapperScan(basePackages = "com.example.security.mapper")
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 防全表更新与删除插件interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());// 分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}}
三、完成登录接口
创建数据库实体类
User.java
package com.example.security.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;/*** 用户* @author heyunlin* @version 1.0*/
@Data
@TableName("user")
public class User implements Serializable {private static final long serialVersionUID = 18L;@TableId(value = "id", type = IdType.INPUT)private String id;/*** 姓名*/private String name;/*** 性别*/private Integer gender;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 手机号*/private String phone;/*** 是否启用*/private Boolean enable;/*** 最后一次登录时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private LocalDateTime lastLoginTime;
}
创建持久层接口
package com.example.security.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.security.entity.User;
import org.springframework.stereotype.Repository;/*** @author heyunlin* @version 1.0*/
@Repository
public interface UserMapper extends BaseMapper<User> {}
创建登录DTO对象
package com.example.security.dto;import lombok.Data;import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;/*** @author heyunlin* @version 1.0*/
@Data
public class UserLoginDTO implements Serializable {private static final long serialVersionUID = 18L;/*** 用户名*/@NotNull(message = "用户名不允许为空")@NotEmpty(message = "用户名不允许为空")private String username;/*** 密码*/@NotNull(message = "密码不允许为空")@NotEmpty(message = "密码不允许为空")private String password;
}
创建控制器类
package com.example.security.controller;import com.example.security.dto.UserLoginDTO;
import com.example.security.entity.User;
import com.example.security.restful.JsonResult;
import com.example.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author heyunlin* @version 1.0*/
@RestController
@RequestMapping(path = "/user", produces = "application/json;charset=utf-8")
public class UserController {private final UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}@RequestMapping(value = "/login", method = RequestMethod.POST)public JsonResult<Void> login(@Validated UserLoginDTO userLoginDTO) {userService.login(userLoginDTO);return JsonResult.success("登录成功");}@RequestMapping(value = "/logout", method = RequestMethod.POST)public JsonResult<Void> logout() {userService.logout();return JsonResult.success("登出成功");}@RequestMapping(value = "/selectById", method = RequestMethod.GET)public JsonResult<User> selectById(@RequestParam(value = "id", required = true) String userId) {User user = userService.selectById(userId);return JsonResult.success(null, user);}}
创建业务层类
UserService接口
package com.example.security.service;import com.example.security.dto.UserLoginDTO;
import com.example.security.entity.User;/*** @author heyunlin* @version 1.0*/
public interface UserService {/*** 登录认证* @param userLoginDTO 用户登录信息*/void login(UserLoginDTO userLoginDTO);/*** 退出登录*/void logout();/*** 通过ID查询用户信息* @param userId 用户ID* @return User 通过ID查询到的用户信息*/User selectById(String userId);
}
UserServiceImpl.java
package com.example.security.service.impl;import com.example.security.dto.UserLoginDTO;
import com.example.security.entity.User;
import com.example.security.mapper.UserMapper;
import com.example.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;/*** @author heyunlin* @version 1.0*/
@Service
public class UserServiceImpl implements UserService {private final UserMapper userMapper;private final AuthenticationManager authenticationManager;@Autowiredpublic UserServiceImpl(UserMapper userMapper, AuthenticationManager authenticationManager) {this.userMapper = userMapper;this.authenticationManager = authenticationManager;}@Overridepublic void login(UserLoginDTO userLoginDTO) {Authentication authentication = new UsernamePasswordAuthenticationToken(userLoginDTO.getUsername(),userLoginDTO.getPassword());authenticationManager.authenticate(authentication);}@Overridepublic void logout() {// todo}@Overridepublic User selectById(String userId) {return userMapper.selectById(userId);}}
自定义登录成功处理器
登陆成功直接重定向到/index.html
package com.example.security.security;import com.example.security.config.SystemProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author heyunlin* @version 1.0*/
public class LoginSuccessHandler implements AuthenticationSuccessHandler {private final SystemProperties systemProperties;public LoginSuccessHandler(SystemProperties systemProperties) {this.systemProperties = systemProperties;}@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {response.sendRedirect(systemProperties.getIndexPage());}}
至此,springboot整合Spring Security就完成了,项目结构如下。
文章就分享到这里了,代码已开源,可按需获取~
springboot整合spring securityhttps://gitee.com/he-yunlin/springboot-springsecurity.git