认证知识请看:SpringSecurity + JWT实现登录认证-CSDN博客
一、RBAC权限模型
在RBAC权限模型下存在用户、角色、权限三个关系,一个角色可以拥有多个权限,一个用户可以是多个角色,所以用户可以同时拥有多个角色的权限。在数据库中表示为用户表、角色表、权限表,同时还应该有两张关联表分别与用户和角色、角色和权限进行关联。
1、用户表
2、角色表
3、权限表
4、用户角色关联表
5、角色权限关联表
6、查询用户拥有权限的SQL
select distinct menuName FROM role r
left join user_role ur on r.roleId = ur.roleId
left join role_menu rm on r.roleId = rm.roleId
left join menu m on rm.menuId = m.menuId
where ur.userId = 1
二、实现授权
1、创建查询权限方法(mapper)
public interface UserMapper extends BaseMapper<User> {
List<String> getAuthentications(int userId);
}
//xml
<mapper namespace="com.zxc.mapper.UserMapper">
<select id="getAuthentications" parameterType="int" resultType="string">
select distinct menuName FROM role r
left join user_role ur on r.roleId = ur.roleId
left join role_menu rm on r.roleId = rm.roleId
left join menu m on rm.menuId = m.menuId
where ur.userId = #{userId}
</select>
</mapper>
2、登录校验时添加权限
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>().eq(User::getName, username);
User user = userMapper.selectOne(queryWrapper);
if(null == user){
throw new RuntimeException("用户名或密码错误");
}
//用户权限
// ArrayList<String> list = new ArrayList<>();
// list.add("test");
// list.add("admin");
Integer id = user.getId();
List<String> list = userMapper.getAuthentications(id);
LoginUser loginUser = new LoginUser(user,list);
return loginUser;
}
}
3、在SecurityContextHolder存入权限
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//从请求头中提取出用户id
String jwt = request.getHeader("token");
//id不存在
if(!StringUtils.hasText(jwt)){
filterChain.doFilter(request,response);
return;
}
//解析jwt
String id = null;
try {
Claims claims = JwtUtils.parseJWT(jwt);
id = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
filterChain.doFilter(request,response);
}
//使用用户id从redis里面提取出用户信息
String jsonstring = redisTemplate.opsForValue().get("userId:" + id);
LoginUser loginUser = JSON.parseObject(jsonstring, LoginUser.class);
//TODO:json转对象后数丢失,重新赋权(暂时未解决)
loginUser.getAuthorityList().clear();
loginUser.getPermission().forEach(p -> loginUser.getAuthorityList().add(new SimpleGrantedAuthority(p)));
//将用户信息存入securityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
4、开启权限认证
开启权限认证:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启授权验证
public class SecurityConfig {
@Bean
public PasswordEncoder PasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
/**
* 配置认证规则
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用basic明文验证
//.httpBasic().disable()
// 前后端分离架构不需要csrf保护
.csrf().disable()
// 禁用默认登录页
//.formLogin().disable()
// 禁用默认登出页
//.logout().disable()
// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
//.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))
// 前后端分离是无状态的,不需要session了,直接禁用。
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
// 允许所有OPTIONS请求
//.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 允许直接访问授权登录接口
.requestMatchers(HttpMethod.POST, "/user/login").anonymous() //匿名
.requestMatchers(HttpMethod.GET,"/*/hello").permitAll() //允许所有
// 允许 SpringMVC 的默认错误地址匿名访问
//.requestMatchers("/error").permitAll()
// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
// 允许任意请求被已登录用户访问,不检查Authority
.anyRequest().authenticated()) //任意用户认证之后都可以访问
//.authenticationProvider(authenticationProvider())
// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
在controller配置权限:
@PreAuthorize("hasAnyAuthority('test')")
@GetMapping("/hello")
public String hello(){
return "hello";
}
三、自定义异常处理
1、认证异常处理
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
//认证异常处理
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//处理异常
ResponseResult responseResult = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "认证失败", authException.getMessage());
String jsonString = JSON.toJSONString(responseResult);
WebUtils.renderString(response,jsonString);
}
}
2、授权异常处理
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
//授权异常处理
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//处理异常
ResponseResult responseResult = new ResponseResult(HttpStatus.FORBIDDEN.value(), "没有访问权限", accessDeniedException.getMessage());
String jsonString = JSON.toJSONString(responseResult);
WebUtils.renderString(response,jsonString);
}
}
3、配置自定义异常处理
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启授权验证
public class SecurityConfig {
@Bean
public PasswordEncoder PasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//认证失败
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
//授权失败
@Autowired
private AccessDeniedHandler accessDeniedHandler;
/**
* 配置认证规则
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用basic明文验证
//.httpBasic().disable()
// 前后端分离架构不需要csrf保护
.csrf().disable()
// 禁用默认登录页
//.formLogin().disable()
// 禁用默认登出页
//.logout().disable()
// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
//.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))
// 前后端分离是无状态的,不需要session了,直接禁用。
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
// 允许所有OPTIONS请求
//.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 允许直接访问授权登录接口
.requestMatchers(HttpMethod.POST, "/user/login").anonymous() //匿名
.requestMatchers(HttpMethod.GET,"/*/hello").permitAll() //允许所有
// 允许 SpringMVC 的默认错误地址匿名访问
//.requestMatchers("/error").permitAll()
// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
// 允许任意请求被已登录用户访问,不检查Authority
.anyRequest().authenticated()) //任意用户认证之后都可以访问
//.authenticationProvider(authenticationProvider())
// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
//配置异常处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) //认证失败
.accessDeniedHandler(accessDeniedHandler); //授权失败
return http.build();
}
}
四、测试
使用postman测试工具
1、使用错误密码登录
2、访问有权限接口
3、访问没有权限的接口
controller:
@PreAuthorize("hasAnyAuthority('test11')")
@GetMapping("/hello")
public String hello(){
return "hello";
}