目录
一、依赖导入
二、添加自定义UserDetail
三、服务层
四、Security配置
五、登录登出的服务层
六、登录参数
七、Security获取登录的User对象
八、控制层
一、依赖导入
<!--springSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
二、添加自定义UserDetail
添加实体类: 数据库表:
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* author: Dragon Wu
* date: 2022-07-19 12:53
*/
/**
* 系统人员
*/
@ApiModel(value="系统人员")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_user")
public class SysUser implements Serializable {
/**
* 主键
*/
@TableId(value = "id")
@ApiModelProperty(value="主键")
private Long id;
/**
* 权限组id
*/
@TableField(value = "jurisdiction_id")
@ApiModelProperty(value="权限组id")
private Long jurisdictionId;
/**
* 用户名
*/
@TableField(value = "username")
@ApiModelProperty(value="用户名")
private String username;
/**
* 昵称
*/
@TableField(value = "nickname")
@ApiModelProperty(value="昵称")
private String nickname;
/**
* 密码
*/
@TableField(value = "password")
@ApiModelProperty(value="密码")
private String password;
/**
* 头像
*/
@TableField(value = "avatar")
@ApiModelProperty(value="头像")
private String avatar;
/**
* 性别: 0女,1男
*/
@TableField(value = "sex")
@ApiModelProperty(value="性别: 0女,1男")
private Boolean sex;
/**
* 手机号
*/
@TableField(value = "mobile")
@ApiModelProperty(value="手机号")
private String mobile;
/**
* 邮箱
*/
@TableField(value = "email")
@ApiModelProperty(value="邮箱")
private String email;
/**
* 账号状态:0停用,1正常
*/
@TableField(value = "stat")
@ApiModelProperty(value="账号状态:0停用,1正常")
private Integer stat;
/**
* 是否删除:0未删除,1已删除
*/
@TableField(value = "is_del")
@ApiModelProperty(value="是否删除:0未删除,1已删除")
private Boolean isDel;
/**
* 创建人id
*/
@TableField(value = "create_by")
@ApiModelProperty(value="创建人id")
private Long createBy;
/**
* 创建时间
*/
@TableField(value = "create_time")
@ApiModelProperty(value="创建时间")
private LocalDateTime createTime;
/**
* 更新人id
*/
@TableField(value = "update_by")
@ApiModelProperty(value="更新人id")
private Long updateBy;
/**
* 更新时间
*/
@TableField(value = "update_time")
@ApiModelProperty(value="更新时间")
private LocalDateTime updateTime;
private static final long serialVersionUID = 1L;
public static final String COL_ID = "id";
public static final String COL_JURISDICTION_ID = "jurisdiction_id";
public static final String COL_USERNAME = "username";
public static final String COL_NICKNAME = "nickname";
public static final String COL_PASSWORD = "password";
public static final String COL_AVATAR = "avatar";
public static final String COL_SEX = "sex";
public static final String COL_MOBILE = "mobile";
public static final String COL_EMAIL = "email";
public static final String COL_STAT = "stat";
public static final String COL_IS_DEL = "is_del";
public static final String COL_CREATE_BY = "create_by";
public static final String COL_CREATE_TIME = "create_time";
public static final String COL_UPDATE_BY = "update_by";
public static final String COL_UPDATE_TIME = "update_time";
}
import com.alibaba.fastjson.annotation.JSONField;
import com.party.domain.SysUser;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* author: Dragon Wu
* date: 2022-07-10 13:39
*/
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private SysUser sysUser;
//用户权限
private List<String> jurisdiction;
//将不会被序列化到redis里
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
public LoginUser(SysUser sysUser,List<String> jurisdiction){
this.sysUser=sysUser;
this.jurisdiction=jurisdiction;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//把jurisdiction里的权限信息字符串封装成SimpleGrantedAuthority对象
if(!Objects.isNull(authorities)){
return authorities;
}
authorities=jurisdiction.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;
}
@Override
public String getPassword() {
return sysUser.getPassword();
}
@Override
public String getUsername() {
return sysUser.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
三、服务层
import com.baomidou.mybatisplus.extension.service.IService;
import com.party.domain.SysUser;
import com.party.model.param.SysUserParam;
import com.party.vo.PageVO;
import java.util.List;
/**
* author: Dragon Wu
* date: 2022-07-19 12:53
*/
public interface SysUserService extends IService<SysUser>{
//密码正则匹配
public static final String PW_PATTERN = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,}$";
//邮箱正则匹配
public static final String EMAIL_MATCHER="[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+";
/**
* 添加系统人员
* @param sysUser 超级管理员
* @param sysUserParam 人员参数
* @return 成功true
* @throws RuntimeException 异常
*/
Boolean insert(SysUser sysUser, SysUserParam sysUserParam) throws RuntimeException;
/**
* 删除人员
* @param sysUser 超级管理员
* @param id 用户id
* @return 成功true
*/
Boolean deleteItem(SysUser sysUser,Long id);
/**
* 更新人员
* @param sysUser 超级管理员
* @param sysUserParam 人员参数
* @return 成功true
*/
Boolean updateItem(SysUser sysUser,SysUserParam sysUserParam);
/**
* 分页查询
* @param sysUser 超级管理员
* @param currentPage 当前页码
* @param pageSize 页面大小
* @return PageVO
*/
PageVO<List<SysUser>> queryPage(SysUser sysUser, Integer currentPage, Integer pageSize);
/**
* 修改人员状态
* @param sysUser 超级管理员
* @param id 禁用人员id
* @param status 账号状态:0停用,1正常
* @return 成功true
*/
Boolean changeStatus(SysUser sysUser,Long id,Integer status);
}
服务实现:
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.party.constant.Constants;
import com.party.domain.SysUser;
import com.party.mapper.SysUserMapper;
import com.party.model.LoginUser;
import com.party.service.JurisdictionConnectService;
import org.springframework.beans.factory.annotation.Autowired;
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.Objects;
/**
* author: Dragon Wu
* date: 2022-07-10 13:30
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private JurisdictionConnectService jurisdictionConnectService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
//查询用户信息
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getIsDel, false)
.eq(SysUser::getStat, Constants.ONE).eq(SysUser::getUsername, userName);
SysUser user = sysUserMapper.selectOne(queryWrapper);
//如果没有查询到用户就抛出异常
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("用户名不存在!");
}
//查询对应的权限信息
// List<String> jurisdiction=new ArrayList<>(Arrays.asList("test","admin"));
List<String> jurisdiction = jurisdictionConnectService.queryJurisdictionByGroupId(user.getJurisdictionId());
//将数据封装到UserDetail返回
return new LoginUser(user, jurisdiction);
}
}
通过这个服务sysUser对象和查询的权限信息会被一起封装成一个UserDetail自定义对象。
四、Security配置
import com.party.filter.JwtAuthenticationTokenFilter;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* author: Dragon Wu
* date: 2022-07-10 13:49
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
//允许匿名访问的
private static final String[] ALLOW_ASK={
"/sys/login",
"/captcha"
};
//总数允许访问的
private static final String[] ALWAYS_ALLOW_ASK={
"/v2/api-docs",
"/swagger-resources/configuration/ui",//用来获取支持的动作
"/swagger-resources",//用来获取api-docs的URI
"/swagger-resources/configuration/security",//安全选项
"/webjars/**",
"/swagger-ui.html",//以上为api文档接口访问路径
"/**/website/**",//拥有该路径的就一直允许访问
};
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
//对应登录接口允许匿名访问
.antMatchers(ALWAYS_ALLOW_ASK).permitAll()
.antMatchers(ALLOW_ASK).anonymous()
//除上面接口全都需要鉴权访问
.anyRequest().authenticated();
//添加过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//添加异常处理器
http.exceptionHandling()
//认证失败的处理器
.authenticationEntryPoint(authenticationEntryPoint)
//授权失败处理器
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
五、登录登出的服务层
import com.party.constant.Constants;
import com.party.domain.SysUser;
import com.party.model.LoginParam;
import com.party.model.LoginUser;
import com.party.service.EasyCaptchaService;
import com.party.service.LoginService;
import com.party.util.JwtUtils;
import com.party.util.RedisUtils;
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.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* author: Dragon Wu
* date: 2022-07-10 17:43
*/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisUtils redisUtils;
@Autowired
private EasyCaptchaService easyCaptchaService;
@Override
public String login(LoginParam loginParam) throws RuntimeException{
//判断验证码是否正确
if(!easyCaptchaService.verifyCaptchaCode(loginParam.getUuid(),loginParam.getCode())){
throw new RuntimeException("验证码错误");
}
//AuthenticationManager authenticate进行用户认证
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(loginParam.getUsername(),loginParam.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过给出相应的提示
if(Objects.isNull(authenticate)){
throw new RuntimeException(Constants.LOGIN_FAIL);
}
//如果认证通过了,使用userId生成JWT
LoginUser loginUser= (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getSysUser().getId().toString();
Map<String,Object> map=new HashMap<>();
map.put(Constants.USER_ID,userId);
String jwt= JwtUtils.createJWT(map);
//判断用户是否已经登录
Object object=redisUtils.getCacheObject(Constants.LOGIN_PRE+userId);
if(!Objects.isNull(object)){
throw new RuntimeException("用户已经登录");
}
//把完整的用户信息存入redis userId作为key, map为value
redisUtils.setCacheObject(Constants.LOGIN_PRE+userId,loginUser,Constants.LOGIN_TIME, TimeUnit.HOURS);
return jwt;
}
@Override
public Boolean logout() {
//获取SecurityContextHolder里的用户id
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
SysUser sysUser=(SysUser) authentication.getPrincipal();
Long id=sysUser.getId();
//删除redis中的值
redisUtils.deleteObject(Constants.LOGIN_PRE+id);
return true;
}
@Override
public Map<String,String> getCaptcha() throws IOException {
return easyCaptchaService.outputCaptchaImg(EasyCaptchaService.CAPTCHA_TYPE_GIF);
}
}
六、登录参数
import com.party.constant.Constants;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
/**
* author: Dragon Wu
* date: 2022-07-12 11:19
*/
@Data
@ApiModel(value = "登录参数")
public class LoginParam {
@NotBlank
@ApiModelProperty(value = "用户名")
private String username;
@NotBlank
@ApiModelProperty(value = "密码")
private String password;
@NotBlank
@Length(min= Constants.CODE_LEN,max = Constants.CODE_LEN)
@ApiModelProperty(value = "验证码")
private String code;
@NotBlank
@ApiModelProperty(value = "获取验证码的uuid")
private String uuid;
}
七、Security获取登录的User对象
import com.party.domain.SysUser;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* author: Dragon Wu
* date: 2022-07-12 15:30
*/
public class SecurityUtils {
public static SysUser getUser(){
return (SysUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
八、控制层
package com.party.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.exceptions.ApiException;
import com.party.constant.Constants;
import com.party.model.LoginParam;
import com.party.model.R;
import com.party.service.LoginService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Map;
/**
* author: Dragon Wu
* date: 2022-07-10 17:34
*/
@Api(tags = "后台登录")
@RestController
@Slf4j
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping("/sys/login")
@ApiOperation(value = "后台登录")
public R<String> login(@Valid @RequestBody LoginParam loginParam) {
try {
return R.ok(loginService.login(loginParam), "登录成功");
} catch (ApiException e2) {
return R.fail(null, e2.getMessage());
} catch (Exception e) {
log.error("后台登录出现异常:" + JSONObject.toJSONString(loginParam), e);
return R.fail(null, Constants.LOGIN_FAIL);
}
}
@GetMapping("/sys/logout")
@ApiOperation(value = "退出登录")
public R<String> logout() {
try {
return loginService.logout() ? R.ok(null, Constants.LOGOUT_OK) : R.fail(null, Constants.LOGOUT_FAIL);
} catch (Exception e) {
log.error("退出登录出现异常:" + e);
return R.fail(null, Constants.LOGOUT_FAIL);
}
}
@ApiOperation(value = "获取验证码")
@GetMapping("/captcha")
public R<Map<String, String>> captcha() {
try {
return R.ok(loginService.getCaptcha(), Constants.GET_SOURCE_OK);
} catch (Exception e) {
log.error("获取验证码出现异常:", e);
return R.fail(null, Constants.GET_SOURCE_FAIL);
}
}
}
登录实现总结完毕