文章目录
前言
本项目采用SpringBoot+SpringSecurity+Jwt+Mybatis,实现了基于数据库的用户认证以及权限分配。
pom.xml导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--自定义的处理类中需要用到-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--注解配置处理器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<!--Swagger相关依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
数据库表结构
/*
Navicat Premium Data Transfer
Source Server : Taylor Swift
Source Server Type : MySQL
Source Server Version : 80023
Source Host : localhost:3306
Source Schema : security
Target Server Type : MySQL
Target Server Version : 80023
File Encoding : 65001
Date: 18/10/2021 18:20:16
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`id` int NOT NULL AUTO_INCREMENT,
`pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求路径匹配规则',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '/db/**');
INSERT INTO `menu` VALUES (2, '/admin/**');
INSERT INTO `menu` VALUES (3, '/user/**');
-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role` (
`id` int NOT NULL AUTO_INCREMENT,
`mid` int NOT NULL COMMENT 'menu表外键',
`rid` int NOT NULL COMMENT 'role表外键',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES (1, 1, 1);
INSERT INTO `menu_role` VALUES (2, 2, 2);
INSERT INTO `menu_role` VALUES (3, 3, 3);
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`nameZh` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'dba', '数据库管理员');
INSERT INTO `role` VALUES (2, 'admin', '系统管理员');
INSERT INTO `role` VALUES (3, 'user', '用户');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NULL DEFAULT NULL,
`locked` tinyint(1) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 0);
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 0);
INSERT INTO `user` VALUES (3, 'user', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 0);
INSERT INTO `user` VALUES (4, 'Sabrina', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 1);
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int NOT NULL AUTO_INCREMENT,
`uid` int NULL DEFAULT NULL,
`rid` int NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 2, 2);
INSERT INTO `user_role` VALUES (4, 3, 3);
SET FOREIGN_KEY_CHECKS = 1;
项目结构图
核心配置类SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
JwtUserDetailService userService;
@Autowired
PermissionFilter permissionFilter;
@Autowired
UserAccessDecisionManager userAccessDecisionManager;
@Bean
PermissionFilter permissionFilter()
{
return new PermissionFilter();
}
@Bean
PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
//注册PermissionFilter和UserAccessDecisionManager进行权限管理
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>()
{
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o)
{
o.setAccessDecisionManager(userAccessDecisionManager);
o.setSecurityMetadataSource(permissionFilter);
return o;
}
})
// 放行静态资源
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// 放行文件访问
.antMatchers("/file/**").permitAll()
// 放行druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
//配置登录路径
.antMatchers(HttpMethod.POST,"/login").permitAll()
//其他请求都需要进行认证
.anyRequest().authenticated()
.and()
//添加登录过滤器,当请求为"login"时该过滤器截取请求
.addFilterBefore(new JwtLoginFilter("/login",
authenticationManager()), UsernamePasswordAuthenticationFilter.class)
//添加token校验过滤器,每次发起请求都需要通过该过滤器进行判断是否处于登录状态
.addFilterBefore(new JwtTokenFilter(),UsernamePasswordAuthenticationFilter.class)
//解决跨域问题
.cors().and().csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception
{
web.ignoring()
.antMatchers("/swagger-ui.html","v2/api-docs")
.antMatchers("/v2/**")
.antMatchers("/swagger-resources/**")
.antMatchers("/webjars/**")
.antMatchers("/*/api-docs")
.antMatchers("/doc.html");
}
}
实体类
public class JwtUserDetails implements UserDetails
{
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean locked;
private List<Role> roles;
private String token;
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
return roles == null ? new ArrayList<SimpleGrantedAuthority>()
: roles.stream().map(role -> new SimpleGrantedAuthority(String.valueOf(role.getRoleId()))).collect(Collectors.toList());
}
@Override
public String getPassword()
{
return this.password;
}
@Override
public String getUsername()
{
return this.password;
}
@Override
public boolean isAccountNonExpired()
{
return true;
}
@Override
public boolean isAccountNonLocked()
{
return !locked;
}
@Override
public boolean isCredentialsNonExpired()
{
return true;
}
@Override
public boolean isEnabled()
{
return enabled;
}
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public Boolean getLocked()
{
return locked;
}
public void setLocked(Boolean locked)
{
this.locked = locked;
}
public String getToken()
{
return token;
}
public void setToken(String token)
{
this.token = token;
}
public List<Role> getRoles()
{
return roles;
}
public void setRoles(List<Role> roles)
{
this.roles = roles;
}
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Menu
{
private Integer id;
/**
* 访问路径
*/
private String pattern;
/**
* 访问当前路径所需要的角色
*/
private List<Role> roles;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role
{
private Integer roleId;
private String name;
/**
* 角色中文名
*/
private String nameZh;
}
工具类
@Component
@Slf4j
public class JwtTokenUtil
{
public final static String CLAIMS_KEY_AUTHORITIES = "authorities";
public final static String SECRET = "Turing-Team";
public final static Long TOKEN_VALIDITY_IN_SECONDS = 600000000L;
public void printLogger(Exception e)
{
log.error("Error:{}",e.getMessage());
}
public Claims getClaimsFromToken(String token)
{
Claims claims;
try
{
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}catch (ExpiredJwtException e)
{
printLogger(e);
claims = null;
}catch (IncorrectClaimException e)
{
printLogger(e);
claims = null;
}
catch (Exception e)
{
printLogger(e);
claims = null;
}
return claims;
}
public String getUsernameFromToken(String token)
{
String username;
try
{
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
}catch (Exception e)
{
printLogger(e);
username = null;
}
return username;
}
public Date getCreateDateFromToken(String token)
{
Date date;
try
{
Claims claims = getClaimsFromToken(token);
date = claims.getIssuedAt();
}catch (Exception e)
{
date = null;
printLogger(e);
}
return date;
}
public Date getExpirationDateFromToken(String token)
{
Date date;
try
{
Claims claims = getClaimsFromToken(token);
date = claims.getExpiration();
}
catch (Exception e)
{
printLogger(e);
date = null;
}
return date;
}
private Date generateExpirationDate()
{
return new Date(System.currentTimeMillis() + TOKEN_VALIDITY_IN_SECONDS);
}
public boolean isTokenExpired(String token)
{
Date date = getExpirationDateFromToken(token);
return getClaimsFromToken(token) == null || date.before(new Date());
}
public String generateToken(String username,StringBuffer roles)
{
Claims claims = new DefaultClaims();
claims.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(generateExpirationDate());
return Jwts.builder()
.setClaims(claims)
.claim(CLAIMS_KEY_AUTHORITIES,roles)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public String refreshToken(String token)
{
String refreshedToken;
try
{
Claims claims = getClaimsFromToken(token);
StringBuffer roles = (StringBuffer) claims.get(CLAIMS_KEY_AUTHORITIES);
refreshedToken = generateToken(claims.getSubject(),roles);
}catch (Exception e)
{
printLogger(e);
refreshedToken = null;
}
return refreshedToken;
}
public boolean validateToken(String token, UserDetails userDetails)
{
JwtUserDetails user = (JwtUserDetails) userDetails;
String username = getUsernameFromToken(token);
return username.equals(user.getUsername()) && !isTokenExpired(token);
}
public Jws<Claims> parserToken(String token)throws JwtException
{
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token);
}
}
@Component
public class SpringInjectUtil implements ApplicationContextAware
{
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringInjectUtil.applicationContext == null){
SpringInjectUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
//根据name
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//根据类型
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name,clazz);
}
}
@Data
@Configuration()
@ConfigurationProperties(prefix = "jwt") //从application.yaml配置中获取
public class JwtSecurityProperties
{
private String header;
private String secret;
private Long tokenValidityInSeconds;
}
#Jwt Configuration
jwt:
header: Authorization
secret: Turing-Team
token_validity-in-seconds: 600000000
用户登录认证
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter
{
@Resource
JwtTokenUtil jwtTokenUtil;
private void init()
{
if (jwtTokenUtil == null)
{
jwtTokenUtil = (JwtTokenUtil) SpringInjectUtil.getBean("jwtTokenUtil");
}
}
public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager)
{
super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
init();
setAuthenticationManager(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException
{
//从请求中获取用户信息
String username = httpServletRequest.getParameter("username");
String password = httpServletRequest.getParameter("password");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
//认证->会调用JwtUserDetailService.loadUserByUsername
return getAuthenticationManager().authenticate(token);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException
{
response.setContentType("application/json;charset=utf-8");
Result<JwtUserDetails> result = new Result<>();
PrintWriter out = response.getWriter();
String responseResult;
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
StringBuffer roles = new StringBuffer();
String username = request.getParameter("username");
JwtUserDetails user = (JwtUserDetails) authResult.getPrincipal();
//遍历用户角色,将其写入token,角色之间用逗号隔开
List<Role> rolesList = user.getRoles();
for (Role role : rolesList)
{
if (role != null)
{
System.out.println(role);
roles.append(role.getRoleId()+",");
}
}
//生成token
String token = jwtTokenUtil.generateToken(username, roles);
user.setToken(token);
result.code(ResultCode.SUCCESS).message("Login Success").data(user);
//将封装好的带有token的用户信息返回前端
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException
{
response.setContentType("application/json;charset=utf-8");
PrintWriter out =response.getWriter();
Result<String> result = new Result<>();
//账户是否过期/被锁定/不可用由SpringSecurity进行检查之后报出异常
//AbstractUserDetailsAuthenticationProvider.check()中进行检查
if (failed instanceof LockedException)
{
result.code(ResultCode.ERROR).message("账号已被锁定,无法进行登录操作!").data("Account had been locked");
}
else if (failed instanceof DisabledException)
{
result.code(ResultCode.ERROR).message("账号不可用,无法进行登录操作!").data("Account is disabled");
}else
{
result.message("用户名或者密码错误,请重新登录!").code(ResultCode.ERROR).data("Login Failure");
}
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
}
Token令牌验证
public class JwtTokenFilter extends GenericFilterBean
{
private static JwtTokenFilter jwtTokenFilter;
@Resource
JwtSecurityProperties jwtSecurityProperties;
@Resource
JwtTokenUtil jwtTokenUtil;
private void init()
{
if (jwtSecurityProperties == null)
{
jwtSecurityProperties = (JwtSecurityProperties) SpringInjectUtil.getBean("jwtSecurityProperties");
}
if (jwtTokenUtil == null)
{
jwtTokenUtil = (JwtTokenUtil) SpringInjectUtil.getBean("jwtTokenUtil");
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
init();
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//从请求头中获取token
String token = request.getHeader(jwtSecurityProperties.getHeader());
PrintWriter out;
Result result;
response.setContentType("application/json;charset=utf-8");
if ("".equals(token) || token == null)
{
out = response.getWriter();
result = new Result<String>();
result.code(ResultCode.UNAUTHORIZED)
.message("必须携带用户的认证信息进行访问")
.data("User Unauthorized");
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
return;
}
if (jwtTokenUtil.isTokenExpired(token))
{
out = response.getWriter();
result = new Result<String>();
result.code(ResultCode.UNAUTHORIZED)
.message("登录认证已过期,请重新登录!")
.data("User Authorization Is Expired");
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
return;
}
Jws<Claims> jws;
try
{
jws = jwtTokenUtil.parserToken(token);
}catch (JwtException e)
{
out = response.getWriter();
result = new Result<String>();
result.code(ResultCode.UNAUTHORIZED)
.message("登录认证无效!")
.data("Invalid Authorization ");
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
return;
}
Claims user = jws.getBody();
//获取用户名
String username = user.getSubject();
//获取用户角色,以逗号作分割的字符串
String authoritiesString = (String) user.get(JwtTokenUtil.CLAIMS_KEY_AUTHORITIES);
//自动转化为GrantedAuthority对象
List<GrantedAuthority> authorities =
AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesString);
//对用户信息进行校验
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(servletRequest, servletResponse);
}
}
获取用户权限
public class PermissionFilter implements FilterInvocationSecurityMetadataSource
{
/**
* 路径匹配类,用于检查用户的请求路径是否与数据库中的对应
*/
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
MenuService menuService;
private void init()
{
if (menuService == null)
{
menuService = (MenuService) SpringInjectUtil.getBean("menuService");
}
}
/**
* 每次用户发送请求都会先进入此方法,分析出该请求地址需要哪些角色权限
* @param o
* @return
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException
{
init();
FilterInvocation filterInvocation = (FilterInvocation) o;
//获取请求地址
String requestUrl = filterInvocation.getRequestUrl();
List<Menu> menus = menuService.findAllMenusWithRoles();
//遍历所有访问路径规则
for (Menu menu : menus)
{
if (antPathMatcher.match(menu.getPattern(),requestUrl))
{
List<Role> roles = menu.getRoles();
String[] rolesArray = new String[roles.size()];
for (int i = 0; i < roles.size(); i++)
{
rolesArray[i] = String.valueOf(roles.get(i).getRoleId());
}
return SecurityConfig.createList(rolesArray);
}
}
//所有的路径都匹配不上,返回默认的标识符,表示该路径是登录即可访问的
return SecurityConfig.createList("Login-Common");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes()
{
return null;
}
/**
* 是否支持该方式,返回true
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass)
{
return true;
}
}
用户权限验证
@Component
public class UserAccessDecisionManager implements AccessDecisionManager
{
/**
* 判断用户是否有访问该路径的权限
* @param authentication 可以获取用户信息
* @param o 实际上是FilterInvocation对象,可以获取请求路径
* @param collection 访问该路径所需要的角色列表
* @throws AccessDeniedException 非法访问异常
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException
{
//遍历访问该路径所需要的所有角色名
for (ConfigAttribute configAttribute : collection)
{
//如果是登录后即可访问
if ("Login-Common".equals(configAttribute.getAttribute()))
{
if (authentication instanceof AnonymousAuthenticationToken)
{
throw new AccessDeniedException("尚未登陆,请前往登录!");
}
return;
}
//获取当前用户的具有的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//遍历该用户的角色并判断该角色是否有权限访问当前请求路径
for (GrantedAuthority authority : authorities)
{
//将用户的角色信息与访问该路径所需要角色进行匹配
if (authority.getAuthority().equals(configAttribute.getAttribute()))
{
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
@Override
public boolean supports(ConfigAttribute configAttribute)
{
return true;
}
@Override
public boolean supports(Class<?> aClass)
{
return true;
}
}
Service层实现类
@Service
public class JwtUserDetailService implements UserDetailsService
{
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
JwtUserDetails jwtUserDetails = userMapper.loadUserByUsername(username);
if (jwtUserDetails == null)
{
throw new UsernameNotFoundException("用户不存在");
}
jwtUserDetails.setRoles(roleMapper.findRolesByUserId(jwtUserDetails.getId()));
return jwtUserDetails;
}
}
@Service
public class MenuServiceImpl implements MenuService
{
@Autowired
MenuMapper menuMapper;
@Override
public List<Menu> findAllMenusWithRoles()
{
return menuMapper.findAllMenuWithRoles();
}
}
@Service
public class RoleServiceImpl implements RoleService
{
@Autowired
JwtUserDetailService userDetailService;
@Autowired
RoleMapper roleMapper;
@Override
public Role findRolesByUsername(String username)
{
JwtUserDetails user = (JwtUserDetails) userDetailService.loadUserByUsername(username);
return roleMapper.findRoleByUserId(user.getId());
}
}
统一响应类
public class Result<T>
{
private Integer code;
private String message;
private T data;
public Result() {}
public Result message(String message)
{
this.message = message;
return this;
}
public Result code(Integer code)
{
this.code = code;
return this;
}
public Result data(T data)
{
this.data = data;
return this;
}
}
Controller
@RestController
@RequestMapping
public class UserController
{
@GetMapping("/hello")
public Result hello()
{
return new Result().message("访问公共接口成功").code(ResultCode.SUCCESS).data("Success");
}
@GetMapping("/db/hello")
public Result db()
{
return new Result().message("访问dba角色接口成功").code(ResultCode.SUCCESS).data("Success");
}
@GetMapping("/admin/hello")
public Result admin()
{
return new Result().message("访问admin接口成功").code(ResultCode.SUCCESS).data("Success");
}
@GetMapping("/user/hello")
public Result user()
{
return new Result().message("访问user接口成功").code(ResultCode.SUCCESS).data("Success");
}
}
流程解析
- 用户发起登录请求之后,会被
JwtLoginFilter
所拦截,进行登录信息验证;登录成功之后会返回用户信息,并且让用户的之后发送来的请求头中携带生成的Token
。 - 用户发起非登录请求之后,会被
JwtTokenFilter
所拦截,解析并验证请求中所带的Token
,判断是否处于已登录状态。 - 已登陆的情况下,会继续被
PermissionFilter
所拦截,判断用户此次访问请求需要具备哪些角色,然后将其封装起来发送至请求访问管理类UserAccessDecisionManager
。 - 请求访问管理类
UserAccessDecisionManager
得到信息后,判断此次访问是否合法,如果合法则放行,否则将抛出异常。
测试
登录测试
登录成功:
账户被锁定:
账户不可用:
账户不存在:
公共接口测试
访问具有访问权限的接口
数据库管理员 :
管理员:
访问无访问权限的接口
用户:
以上,整个登录认证以及权限控制功能已经搭建好,可以在此基础上根据需求添加其他的业务。
该文仅作为本人学习使用,水平与能力有限,如有错误或不足欢迎指正!