基于RBAC模型(Role-Based Access Control,基于角色的访问控制)建表实现菜单功能权限控制;
Spring Security+Redis实现分布式Session鉴权。
RBAC模型建表
image.png
以会员系统为例,具体的表结构如下:
用户表:
image.png
角色表:
image.png
用户角色表:
image.png
权限表:
image.png
角色权限表:
image.png
分布式Session权限
Spring Security关键相关代码:
Spring Security配置类示例代码,SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UsernameAuthenticationProvider usernameAuthenticationProvider;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(usernameAuthenticationProvider);
}
/**
* 安全配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 开启授权认证
// 开启跨域共享,关闭同源策略【允许跨域】
// 跨域伪造请求=无效,
http.cors().and().csrf().disable();
// 权限配置; 定义哪些URL需要被保护、哪些不需要被保护;设置所有人都可以访问登录页面; 任何请求,登录后可以访问
http.authorizeRequests().antMatchers("/api/manager/login", "/api/manager/initW1Company").permitAll()
.anyRequest()
.authenticated();
http.sessionManagement()
// 设置Session的创建策略为:会在需要时创建一个HttpSession(默认值也是这个)
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
// 异常处理
.exceptionHandling()
// 匿名用户访问无权限资源时的异常
.authenticationEntryPoint(new LoginAuthenticationEntryPoint());
}
/**
* 跨域配置
*
* @return 基于URL的跨域配置信息
*/
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 注册跨域配置
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
认证类示例代码UsernameAuthenticationProvider.java,
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Set;
/**
* @author caobing
* @date 2021/4/7
*/
@Component
@Slf4j
public class W1UsernameAuthenticationProvider implements AuthenticationProvider {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private ISysMenuService menuService;
@Autowired
private SysUserRoleMapper sysUserRoleMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 用户名 密码
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// TODO 验证用户名密码是否匹配
// 验证成功后反,返回相关用户信息
UserDetailsModel retModel = new UserDetailsModel();
SysUser sysUser = new SysUser();
retModel.setSysUser(sysUser);
// 设置菜单功能权限
Long userId = sysUser.getUserId();
List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
List<RouterVo> routers = menuService.buildMenus(menus);
if (CollectionUtils.isEmpty(routers)) {
throw new ServiceException("登录账号还未开通权限,请联系机构管理员");
}
retModel.setRouters(routers);
// 权限集合
Set<String> permissions = permissionService.getMenuPermission(sysUser.getUserId());
retModel.setPermissions(permissions);
return new UsernamePasswordAuthenticationToken(retModel, password, retModel.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
无权限时报错示例代码LoginAuthenticationEntryPoint.java
import com.alibaba.fastjson.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
public class LoginAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.OK.value());
InnerResult innerResult = new InnerResult(401, "登陆失效,请重新登录");
response.getWriter().print(JSONObject.toJSONString(innerResult));
}
}
分布式session支持:
maven依赖, 需要找到合适的版本号:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring session的依赖 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置类RedisSessionConfig.java
···
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600 * 12, redisNamespace = "spring:session:manager")
public class RedisSessionConfig {
}
···