Spring Security是Spring官方推荐的认证、授权框架,功能相比Apache Shiro功能更丰富也更强大,但是使用起来更麻烦。
如果使用过Apache Shiro,学习Spring Security会比较简单一点,两种框架有很多相似的地方。
目录
一、准备工作
创建springboot项目
pom.xml
application.yml
二、创建相关的类
UserDetailsService
SecurityConfig.java
SystemProperties.java
MybatisPlusConfig.java
三、完成登录接口
创建数据库实体类
创建持久层接口
创建登录DTO对象
创建控制器类
创建业务层类
自定义登录成功处理器
首先,通过IntelliJ IDEA创建一个springboot项目,项目名为springboot-springsecurity,在pom.xml中添加相关依赖。
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
com.example
springboot-springsecurity
0.0.1-SNAPSHOT
1.8
0.9.1
8.0.28
1.1.21
1.18.22
2.2.2
3.5.1
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-validation
org.springframework.boot
spring-boot-starter-security
mysql
mysql-connector-java
${mysql.version}
com.alibaba
druid
${druid.version}
org.projectlombok
lombok
${lombok.version}
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis.version}
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
io.jsonwebtoken
jjwt
${jjwt.version}
org.springframework.boot
spring-boot-maven-plugin
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/spring_security
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
mapper-locations: classpath:mapper/*Mapper.xml
logging:
level:
springfox: error
com.example.security: debug
system:
login-page: /login.html
login-url: /user/login
index-page: /index.html
logout-url: /user/logout
parameter:
username: username
password: password
white-url:
- /js/**
- /css/**
- /images/**
- /user/login
- /login.html
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;
@Autowired
public UserDetailsServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
User user = selectByUsername(username);
if (user == null) {
throw new BadCredentialsException("登录失败,用户名不存在!");
} else {
List 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 wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
List list = userMapper.selectList(wrapper);
if (list.size() == 1) {
return list.get(0);
}
return null;
}
/**
* 通过用户名查询用户权限
* @param username 用户名
* @return List
*/
private List selectPermissions(String username) {
if (username == null) {
throw new GlobalException(ResponseCode.BAD_REQUEST, "用户名不能为空");
}
List permissions = new ArrayList<>();
permissions.add("/user/login");
permissions.add("/user/logout");
permissions.add("/user/selectById");
return permissions;
}
}
创建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;
@Autowired
public SecurityConfig(SystemProperties systemProperties) {
this.systemProperties = systemProperties;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return (String) charSequence;
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.equals(s);
}
};
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected 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();
}
}
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 whiteUrl;
/**
* 登录的参数
*/
private Map parameter;
}
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 {
@Bean
public 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 {
}
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;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public JsonResult login(@Validated UserLoginDTO userLoginDTO) {
userService.login(userLoginDTO);
return JsonResult.success("登录成功");
}
@RequestMapping(value = "/logout", method = RequestMethod.POST)
public JsonResult logout() {
userService.logout();
return JsonResult.success("登出成功");
}
@RequestMapping(value = "/selectById", method = RequestMethod.GET)
public JsonResult 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;
@Autowired
public UserServiceImpl(UserMapper userMapper, AuthenticationManager authenticationManager) {
this.userMapper = userMapper;
this.authenticationManager = authenticationManager;
}
@Override
public void login(UserLoginDTO userLoginDTO) {
Authentication authentication = new UsernamePasswordAuthenticationToken(
userLoginDTO.getUsername(),
userLoginDTO.getPassword()
);
authenticationManager.authenticate(authentication);
}
@Override
public void logout() {
// todo
}
@Override
public 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;
}
@Override
public 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