一、引入相关依赖
<!--spring security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--常用工具类依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
二、在application.yml中配置相关信息
security:
jwt:
expire: 604800
secret: admin
header: Authorization
三、添加jwtutil工具类
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "security.jwt")
public class JwtUtil {
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
private String secret;
private Long expire;
/**
* 根据负责生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取JWT中的负载
*/
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
log.info("JWT格式验证失败:{}",token);
}
return claims;
}
/**
* 生成token的过期时间
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expire * 1000);
}
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param userDetails 从数据库中查询出来的用户信息
*/
public boolean validateToken(String token, UserDetailImpl userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*/
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 从token中获取过期时间
*/
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 根据用户信息生成token
*/
public String generateToken(UserDetailImpl userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String token) {
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
}
/**
* response响应头等信息设置
*/
public class WebUtils {
public static String renderString(HttpServletResponse response,String string){
try{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().println(string);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
四、编写UserDetails实现类UserDetailImpl
@Data
@NoArgsConstructor
public class UserDetailImpl implements UserDetails {
private SysUser user;
private List<String> permission;
public UserDetailImpl(SysUser user, List<String> permission) {
this.user = user;
this.permission = permission;
}
@JSONField(serialize = false)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return permission.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.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;
}
}
五、编写UserDetailsService实现类UserDetailsServiceImpl
@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
SysUserMapper userMapper;
@Autowired
SysMenuMapper sysMenuMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//根据用户名查询用户信息
SysUserExample sysUserExample = new SysUserExample();
sysUserExample.createCriteria().andUsernameEqualTo(s);
List<SysUser> sysUsers = userMapper.selectByExample(sysUserExample);
//判断是否存在
if(sysUsers == null || sysUsers.size() == 0 ){
throw new RuntimeException("用户名或密码错误");
}
//存在再把用户权限信息查询出来
SysUser sysUser = sysUsers.get(0);
log.info("当前用户信息为>>>{}"+sysUser);
List<String> menus = sysMenuMapper.selectMenusByUserId(sysUser.getId());
//封装到UserDetails对象中去,并返回
return new UserDetailImpl(sysUser,menus);
}
}
六、编写登录接口login
@PostMapping("/login")
public ResultVo login(@RequestBody User user){
String token = userService.login(user.getUsername(),user.getPassword());
Map<String, String> map = new HashMap<>();
//返回前端token对应的key,这里用的Authorization
map.put("key",header);
//返回前端的token值
map.put("token",token);
return ResultVo.succ(map);
}
七、在登录实现类接口中进行相关认证操作
@Service
public class SysUserListImpl implements SysUserService {
@Autowired
private SysUserMapper userMapper;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
JwtUtil jwtUtil;
@Autowired
RedisUtil redisUtil;
@Override
public ResultVo selectUserList() {
List<SysUser> users = userMapper.selectByExample(null);
return ResultVo.succ(users);
}
@Override
public String login(String username, String password) {
//将前端传入的用户名、密码封装到UsernamePasswordAuthenticationToken对象中
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username,password);
//调用AuthenticationManager的authenticate方法,将usernamepassword传入
//security会自动调用我们写的UserDetailServiceImpl类中的loadUserByUsername方法
//去数据库中查找用户信息,进行认证,认证结果会封装到authenticate中
Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
//判断是否认证成功
if(Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
//认证成功则根据用户名等信息生成token
UserDetailImpl user = (UserDetailImpl) authenticate.getPrincipal();
String token = jwtUtil.generateToken(user);
//用户信息存redis
redisUtil.set("login:"+user.getUser().getUsername(),user);
return token;
}
}
八、配置认证失败处理器AuthenticationEntryPointImpl
/**
* 认证失败处理器
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//封装认证失败信息
ResultVo resultVo = new ResultVo(HttpStatus.UNAUTHORIZED.value(), "认证失败,请重新登录", null);
String result = JSON.toJSONString(resultVo);
WebUtils.renderString(httpServletResponse,result);
}
}
九、配置自定义token过滤器
/**
* token过滤器
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${security.jwt.header}")
private String header;
@Autowired
JwtUtil jwtUtil;
@Autowired
RedisUtil redisUtil;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//获取请求头中的token
String token = httpServletRequest.getHeader(header);
if(StringUtils.isEmpty(token)){
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
//解析token
String username = jwtUtil.getUserNameFromToken(token);
if(StringUtils.isEmpty(username)){
throw new RuntimeException("token异常");
}
//从redis查询用户信息
UserDetailImpl userDetail = (UserDetailImpl) redisUtil.get("login:" + username);
//存入SecurityContext中
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
十、 配置无权访问处理器AccessDeniedImpl
/**
* 无访问权限
*/
@Component
public class AccessDeniedImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
//封装无访问权限信息
ResultVo resultVo = new ResultVo(HttpStatus.FORBIDDEN.value(), "无访问权限", null);
String result = JSON.toJSONString(resultVo);
WebUtils.renderString(httpServletResponse,result);
}
}
十一、配置securityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//放行白名单,根据需求自行添加
public static final String[] WHITE_URL = {
"/sys-user/login"
};
@Autowired
AuthenticationEntryPointImpl authenticationEntryPointImpl;
@Autowired
AccessDeniedImpl accessDeniedImpl;
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//security跨域开启
.cors()
.and()
//csrf关闭
.csrf().disable()
//session关闭
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//放行白名单
.authorizeRequests()
.antMatchers(WHITE_URL).permitAll()
.anyRequest().authenticated()
.and()
//异常处理器
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPointImpl)
.accessDeniedHandler(accessDeniedImpl)
.and()
//自定义过滤器
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
/**
* 暴露AuthenticationManager
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
十二、授权接口通过注解来实现
/**
* @PreAuthorize("hasAuthority('sys:user:list')")表示拥有sys:user:list权限才可以访问
* @param num
* @param size
* @return
*/
@PreAuthorize("hasAuthority('sys:user:list')")
@GetMapping("/selectUserList")
public ResultVo selectUserList(Integer num,Integer size){
PageHelper.startPage(num,size);
return userService.selectUserList();
}
十三、对应数据库表设计
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`username` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`password` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`avatar` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`email` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`city` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`created` datetime(0) NULL DEFAULT NULL,
`updated` datetime(0) NULL DEFAULT NULL,
`last_login` datetime(0) NULL DEFAULT NULL,
`statu` int(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `UK_USERNAME`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, '刘备', '$2a$10$evqEBqCgh1chs/9LP5MORuVzHIN.AH9.wGoB69t2SfX6Uowjb1Qo6', NULL, NULL, NULL, NULL, NULL, NULL, 1);
INSERT INTO `sys_user` VALUES (2, '关羽', '$2a$10$evqEBqCgh1chs/9LP5MORuVzHIN.AH9.wGoB69t2SfX6Uowjb1Qo6', NULL, NULL, NULL, NULL, NULL, NULL, 1);
INSERT INTO `sys_user` VALUES (3, '张飞', '$2a$10$evqEBqCgh1chs/9LP5MORuVzHIN.AH9.wGoB69t2SfX6Uowjb1Qo6', NULL, NULL, NULL, NULL, NULL, NULL, 1);
INSERT INTO `sys_user` VALUES (4, '赵云', '$2a$10$evqEBqCgh1chs/9LP5MORuVzHIN.AH9.wGoB69t2SfX6Uowjb1Qo6', NULL, NULL, NULL, NULL, NULL, NULL, 1);
INSERT INTO `sys_user` VALUES (5, '马超', '$2a$10$R7t7j0.OM/J6EgQGaXQ7.OavYHffX9SnNbnCoJD49ySbAb1A5k9x2', NULL, NULL, NULL, NULL, NULL, NULL, 1);
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`code` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`remark` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注',
`created` datetime(0) NULL DEFAULT NULL,
`updated` datetime(0) NULL DEFAULT NULL,
`statu` int(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name`) USING BTREE,
UNIQUE INDEX `code`(`code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '系统管理员', 'admin', NULL, NULL, NULL, 1);
INSERT INTO `sys_role` VALUES (2, '用户管理员', 'users', NULL, NULL, NULL, 1);
INSERT INTO `sys_role` VALUES (3, '角色管理员', 'roles', NULL, NULL, NULL, 1);
INSERT INTO `sys_role` VALUES (4, '菜单管理员', 'menus', NULL, NULL, NULL, 1);
INSERT INTO `sys_role` VALUES (5, '系统工具', 'tools', NULL, NULL, NULL, 1);
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(0) NULL DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`path` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`component` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`type` int(0) NOT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '菜单图标',
`orderNum` int(0) NULL DEFAULT NULL COMMENT '排序',
`created` datetime(0) NULL DEFAULT NULL,
`updated` datetime(0) NULL DEFAULT NULL,
`statu` int(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, NULL, '系统管理员', '', 'sys:manage', '', 0, 'el-icon-s-operation', NULL, NULL, NULL, 1);
INSERT INTO `sys_menu` VALUES (2, 1, '用户管理员', '/sys/users', 'sys:user:list', '', 0, 'el-icon-s-custom', NULL, NULL, NULL, 1);
INSERT INTO `sys_menu` VALUES (3, 1, '角色管理员', 'sys/roles', 'sys:role:list', '', 0, 'el-icon-rank', NULL, NULL, NULL, 1);
INSERT INTO `sys_menu` VALUES (4, 1, '菜单管理员', '/sys/menus', 'sys:menu:list', '', 0, 'el-icon-menu', NULL, NULL, NULL, 1);
INSERT INTO `sys_menu` VALUES (5, NULL, '系统工具', '/sys/tools', 'sys:tool:list', '', 0, 'el-icon-tool', NULL, NULL, NULL, 1);
INSERT INTO `sys_menu` VALUES (6, NULL, '添加用户', '', 'sys:user:save', '', 2, '', NULL, NULL, NULL, 1);
INSERT INTO `sys_menu` VALUES (7, NULL, '编辑用户', '', 'sys:user:update', '', 2, '', NULL, NULL, NULL, 1);
INSERT INTO `sys_menu` VALUES (8, NULL, '删除用户', '', 'sys:user:delete', '', 2, '', NULL, NULL, NULL, 1);
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`user_id` bigint(0) NOT NULL,
`role_id` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2, 2);
INSERT INTO `sys_user_role` VALUES (3, 3, 3);
INSERT INTO `sys_user_role` VALUES (4, 4, 4);
INSERT INTO `sys_user_role` VALUES (5, 5, 5);
INSERT INTO `sys_user_role` VALUES (6, 5, 2);
INSERT INTO `sys_user_role` VALUES (7, 5, 3);
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`role_id` bigint(0) NOT NULL,
`menu_id` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 102 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (1, 1, 1);
INSERT INTO `sys_role_menu` VALUES (2, 1, 2);
INSERT INTO `sys_role_menu` VALUES (3, 1, 3);
INSERT INTO `sys_role_menu` VALUES (4, 1, 4);
INSERT INTO `sys_role_menu` VALUES (5, 1, 5);
INSERT INTO `sys_role_menu` VALUES (6, 1, 6);
INSERT INTO `sys_role_menu` VALUES (7, 1, 7);
INSERT INTO `sys_role_menu` VALUES (8, 1, 8);
INSERT INTO `sys_role_menu` VALUES (9, 2, 2);
INSERT INTO `sys_role_menu` VALUES (10, 3, 3);
INSERT INTO `sys_role_menu` VALUES (11, 4, 4);
INSERT INTO `sys_role_menu` VALUES (12, 5, 5);
十四、进行相关测试(其中用的的redisUtil工具类相关代码看整合redis部分)
标签:springboot,INTO,jwt,sys,token,VALUES,user,security,NULL From: https://www.cnblogs.com/wlfs000000/p/17378408.html