spring security多个权限_在实战中学习Springboot+Security+redis+jwt的登录流程

一、环境准备

  • vm+ubuntu/centos(win环境下也行)

  • docker + redis(自行百度)+Redis Desktop Manager

  • idea

二、初始化项目

我们在Spring Initializr中初始化

2e635195cdba302ecce91a678ad37f1b.png

勾选Spring Web和Spring Security

c962cca4242ec2585da1edd82be0bb5e.png

2537057c7ad03dea47913b98afef2bbb.png

(一)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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.6.RELEASEversion>
<relativePath/>
parent>
<groupId>com.ssrmjgroupId>
<artifactId>login-demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>login-demoname>
<description>Demo project for Spring Bootdescription>

<properties>
<java.version>1.8java.version>
properties>

<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>


<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>


<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>


<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.6.0version>
dependency>


<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-testartifactId>
<scope>testscope>
dependency>
dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>

project>

(二)yml配置

spring:
mvc:
throw-exception-if-no-handler-found: true
resources:
add-mappings: false

###Redis
redis:
host: linux的ip
port: 6379
timeout: 2000ms
password: redis密码 #密码
jedis:
pool:
max-active: 10
max-idle: 8
min-idle: 2
max-wait: 1000ms

logging:
level:
org.springframework.security: info
root: info
path: e:/log/login-demo-log

### jwt
jwt:
###过期时间 单位s
time: 1800
###安全密钥
secret: "BlogSecret"
###token前缀
prefix: "Bearer "
###http头key
header: "Authorization"

(三)项目结构

d858e975d29275e5a33d9799a7cbe167.png

(四)model层

注:setter、getter和toString采用lombok
entity.Result(返回结果实体类)

package com.ssrmj.model.entity;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.ToString;

/**
* @Description: 返回结果实体类
* @Author: Mt.Li
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
@ToString
public class Result {

private Integer code; // 返回状态码

private String message; // 返回信息

private Object data; // 返回数据

private Result(){

}

public Result(Integer code, String message) {
super();
this.code = code;
this.message = message;
}

public Result(Integer code, String message, Object data) {
super();
this.code = code;
this.message = message;
this.data = data;
}

public static Result create(Integer code, String message){
return new Result(code,message);
}

public static Result create(Integer code, String message, Object data){
return new Result(code,message,data);
}
}

entity.StatusCode(自定义状态码)

package com.ssrmj.model.entity;

/**
* 自定义状态码
*/
public class StatusCode {
// 操作成功
public static final int OK = 200;

// 失败
public static final int ERROR = 201;

// 用户名或密码错误
public static final int LOGINERROR = 202;

// token过期
public static final int TOKENEXPIREE = 203;

// 权限不足
public static final int ACCESSERROR = 403;

// 远程调用失败
public static final int REMOTEERROR = 204;

// 重复操作
public static final int REPERROR = 205;

// 业务层错误
public static final int SERVICEERROR = 500;

// 资源不存在
public static final int NOTFOUND = 404;

}

pojo.Role(角色)

package com.ssrmj.model.pojo;

import lombok.Data;
import lombok.ToString;

/**
* @Description: 角色
* @Author: Mt.Li
*/

@Data
@ToString
public class Role {

private Integer id;//角色id
private String name;//角色名

}

pojo.User(用户)

package com.ssrmj.model.pojo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.ToString;

import java.io.Serializable;
import java.util.List;

/**
* @Description: 用户
* @Author: Mt.Li
*/

@Data
@ToString
public class User implements Serializable {

// 自动生成的serialVersionUID
private static final long serialVersionUID = 7015283901517310682L;

private Integer id;

private String name;

private String password;

// 用户状态,0-封禁,1-正常
private Integer state;

@JsonIgnore
private List roles;
}

注:代码中自动生成的serialVersionUID

(五)config

1、BeanConfig(将一些不方便加@Component注解的类放在此处) 
什么意思呢,就是有的类我们用@Autowired注入的时候,spring不能识别,于是在这里写成方法注入容器

package com.ssrmj.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
* 将一些不方便加@Component注解的类放在此处加入spring容器
*/

@Component
public class BeanConfig {

/**
* spring-security加密方法
*/
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}

/**
* spring-boot内置的json工具
*/
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
}

}

2、JwtConfig(Jwt配置类,将yml中的配置引入)

package com.ssrmj.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@ConfigurationProperties(prefix = "jwt")
@Component
public class JwtConfig {
public static final String REDIS_TOKEN_KEY_PREFIX = "TOKEN_";
private long time; // 过期时间
private String secret; // JWT密码
private String prefix; // Token前缀
private String header; // 存放Token的Header Key

public long getTime() {
return time;
}

public void setTime(long time) {
this.time = time;
}

public String getSecret() {
return secret;
}

public void setSecret(String secret) {
this.secret = secret;
}

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

public String getHeader() {
return header;
}

public void setHeader(String header) {
this.header = header;
}
}

3、WebSecurityConfig(Security拦截配置)

package com.ssrmj.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

/**
* @Description:
* @Author: Mt.Li
*/

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启Spring方法级安全,开启前置注解,同样也是开启了Security注解模式
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {

//禁用csrf
//options全部放行
//post 放行
httpSecurity.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.POST).permitAll() // 为了方便测试,放行post
.antMatchers(HttpMethod.PUT).authenticated()
.antMatchers(HttpMethod.DELETE).authenticated()
.antMatchers(HttpMethod.GET).authenticated();

httpSecurity.headers().cacheControl();
}

}

六)util

JwtTokenUtil(关于token操作的工具类)

package com.ssrmj.util;

import com.ssrmj.config.JwtConfig;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.*;

@Component
public class JwtTokenUtil implements Serializable {

private static final long serialVersionUID = 7965205899118624911L;

private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
private static final String CLAIM_KEY_ROLES = "roles";

@Autowired
private JwtConfig jwtConfig;

public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long)claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
}

/**
* 从token中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}

private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}

/**
* 生成过期时间 单位[ms]
*
*/
private Date generateExpirationDate() {
// 当前毫秒级时间 + yml中的time * 1000
return new Date(System.currentTimeMillis() + jwtConfig.getTime() * 1000);
}

/**
* 根据提供的用户详细信息生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(3);
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); // 放入用户名
claims.put(CLAIM_KEY_CREATED, new Date()); // 放入token生成时间
List<String> roles = new ArrayList<>();
Collection extends GrantedAuthority> authorities = userDetails.getAuthorities();
for (GrantedAuthority authority : authorities) { // SimpleGrantedAuthority是GrantedAuthority实现类
// GrantedAuthority包含类型为String的获取权限的getAuthority()方法
// 提取角色并放入List中
roles.add(authority.getAuthority());
}
claims.put(CLAIM_KEY_ROLES, roles); // 放入用户权限

return generateToken(claims);
}

/**
* 生成token(JWT令牌)
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
.compact();
}

}

(七)dao层

结构图:73c689f41a8cf46d1fafa3eeac66164f.png

RoleDao

package com.ssrmj.dao;

import com.ssrmj.model.pojo.Role;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface RoleDao {
/**
* 根据用户id查询角色
*/
List<Role> findUserRoles(Integer id);

}

UserDao

package com.ssrmj.dao;

import com.ssrmj.model.pojo.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserDao {

/**
* 根据用户名查询用户
*/
User findUserByName(String name);

}

RoleDaoImpl

package com.ssrmj.dao.impl;

import com.ssrmj.dao.RoleDao;
import com.ssrmj.model.pojo.Role;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
* @Description:
* @Author: Mt.Li
*/
@Service
public class RoleDaoImpl implements RoleDao {

private List roles = new ArrayList<>();private static Role r1 = new Role();private static Role r2 = new Role();@Overridepublic ListfindUserRoles(Integer id) {if(id == 1) {
r1.setId(0);
r1.setName("ADMIN");
r2.setId(1);
r2.setName("USER");
roles.add(r1);
roles.add(r2);return roles;
}return null;
}
}

UserDaoImpl

package com.ssrmj.dao.impl;

import com.ssrmj.dao.UserDao;
import com.ssrmj.model.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @Description:
* @Author: Mt.Li
*/
@Service
public class UserDaoImpl implements UserDao {

@Autowired
RoleDaoImpl roleDaoImpl;

@Override
public User findUserByName(String name) {
User user = new User();
user.setId(1);
user.setName("admin");
user.setPassword("123456");
user.setState(1);
user.setRoles(roleDaoImpl.findUserRoles(user.getId()));
return user;
}
}

(八)service

LoginService

package com.ssrmj.service;

import com.ssrmj.config.JwtConfig;
import com.ssrmj.dao.impl.RoleDaoImpl;
import com.ssrmj.dao.impl.UserDaoImpl;
import com.ssrmj.model.pojo.Role;
import com.ssrmj.model.pojo.User;
import com.ssrmj.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.*;
import java.util.concurrent.TimeUnit;

/** * @Description: * @Author: Mt.Li
*/

@Service
public class LoginService implements UserDetailsService {

@Autowired
UserDaoImpl userDao;

@Autowired
RoleDaoImpl roleDao;

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Autowired
private JwtConfig jwtConfig;

public Map login(User user) throws RuntimeException{
User dbUser = this.findUserByName(user.getName());
// 用户不存在 或者 密码错误
if (dbUser == null || !dbUser.getName().equals("admin") || !dbUser.getPassword().equals("123456")) {
throw new UsernameNotFoundException("用户名或密码错误");
}

// 用户已被封禁
if (0 == dbUser.getState()) {
throw new RuntimeException("你已被封禁");
}

// 用户名 密码匹配,获取用户详细信息(包含角色Role)
final UserDetails userDetails = this.loadUserByUsername(user.getName());

// 根据用户详细信息生成token
final String token = jwtTokenUtil.generateToken(userDetails);
Collection extends GrantedAuthority> authorities = userDetails.getAuthorities();
List<String> roles = new ArrayList<>();
for (GrantedAuthority authority : authorities) { // SimpleGrantedAuthority是GrantedAuthority实现类
// GrantedAuthority包含类型为String的获取权限的getAuthority()方法
// 提取角色并放入List中
roles.add(authority.getAuthority());
}

Map<String, Object> map = new HashMap<>(3);

map.put("token", jwtConfig.getPrefix() + token);
map.put("name", user.getName());
map.put("roles", roles);

//将token存入redis(TOKEN_username, Bearer + token, jwt存放五天 过期时间) jwtConfig.time 单位[s]
redisTemplate.opsForValue().
set(JwtConfig.REDIS_TOKEN_KEY_PREFIX + user.getName(), jwtConfig.getPrefix() + token, jwtConfig.getTime(), TimeUnit.SECONDS);

return map;

}

/** * 根据用户名查询用户 */
public User findUserByName(String name) {
return userDao.findUserByName(name);
}

/** * 根据用户名查询用户 */
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
User user = userDao.findUserByName(name);
// 新建权限集合,SimpleGrantedAuthority是GrantedAuthority实现类
List authorities = new ArrayList<>(1);//用于添加用户的权限。将用户权限添加到authoritiesList roles = roleDao.findUserRoles(user.getId()); // 查询该用户的角色for (Role role : roles) {// 将role的name放入权限的集合
authorities.add(new SimpleGrantedAuthority(role.getName()));
}return new org.springframework.security.core.userdetails.User(user.getName(), "***********", authorities);
}
}

(九)controller

UserController

package com.ssrmj.controller;

import com.ssrmj.model.entity.Result;
import com.ssrmj.model.entity.StatusCode;
import com.ssrmj.model.pojo.User;
import com.ssrmj.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
* @Description:
* @Author: Mt.Li
*/

@RestController
@RequestMapping("/user")
public class UserController {

@Autowired
private LoginService loginService;

/**
* 登录返回token
*/
@PostMapping("/login")
public Result login(User user) {

try {
Map map = loginService.login(user);
return Result.create(StatusCode.OK, "登录成功", map);
} catch (UsernameNotFoundException e) {
return Result.create(StatusCode.LOGINERROR, "登录失败,用户名或密码错误");
} catch (RuntimeException re) {
return Result.create(StatusCode.LOGINERROR, re.getMessage());
}
}

}

测试

测试我们用postman模拟请求

aa6c9a4762af136471b2ad56b9304fe1.png

点击Send,得到响应如下

97d85a1ae1b5dd052cbd498bb125e06d.png

我们利用Redis Desktop Manager查看redis数据库的情况

8bd99e61fc6f3e16f55d7036ab95ae9a.png

由于redis是基于内存的数据库,存取速度很快,并且有可持久化的特性,用来存储token再合适不过了。 
注:博主才疏学浅,如有错误,请及时说明,谢谢。

c554b4f97a1fab40e345a5768be20568.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值