前言:
参考文章:
项目主要代码来源于参考的第一篇文章,jwt来源于第二篇
改变:
- 将第一篇文章的项目改成我熟悉的无xml文件模式
- 将第二篇文章的jwt加入第一篇文章代码
- 代码部分修改…
项目结构
一.基于权限控制
- 编写controller接口
a.管理员资源接口,需要有管理员相关权限或角色才可以访问
/**
* @description: 管理员资源接口,需要有管理员相关权限或角色才可以访问
* @author: ※狗尾巴草
* @date: 2020-11-14 18:09
**/
@RestController
@RequestMapping("admin")
public class adminController {
@GetMapping("info")
public String getCommon(){
return "admin资源";
}
}
b.普通用户资源接口,需要有普通相关权限或角色才可以访问
/**
* @description: 普通用户资源接口,需要有普通相关权限或角色才可以访问
* @author: ※狗尾巴草
* @date: 2020-11-13 12:43
**/
@RestController
@RequestMapping("user")
public class userController {
@GetMapping("getUser")
public String getUser(){
return "getUser";
}
}
c.公共资源接口,无需权限或绝便可访问
/**
* @description: 公共资源接口,无需权限或绝便可访问
* @author: ※狗尾巴草
* @date: 2020-11-13 14:29
**/
@RestController
@RequestMapping("common")
public class commonCOntroller {
@GetMapping("info")
public String getCommon(){
return "公共资源";
}
}
-
在数据库添加接口以及权限相关数据
a.接口,sys_request_path表
b.角色,sys_role表
c.权限
-
登录成功处理器,CustomizeAuthenticationSuccessHandler
添加JWT,将t权限信息保存到oken中,待会验证时再取出权限进行鉴权
/**
* @description: 登录成功处理器
* @author: ※狗尾巴草
* @date: 2020-11-13 14:19
**/
@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private SysUserService sysUserService;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//更新用户表上次登录时间、更新人、更新时间等字段
User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
SysUserEntity sysUser = sysUserService.selectByName(userDetails.getUsername());
sysUser.setLast_login_time(new Date());
sysUser.setUpdate_time(new Date());
sysUser.setUpdate_user(sysUser.getId());
sysUserService.updateById(sysUser);
//此处还可以进行一些处理,比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限,
//进而前台动态的控制菜单的显示等,具体根据自己的业务需求进行扩展
// 获取用户权限
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
ArrayList<String> list = new ArrayList<>();
String role = "";
for (GrantedAuthority authority : authorities){
role += authority.getAuthority()+",";
list.add(authority.getAuthority());
}
role=role.substring(0,role.length()-1);
//将权限放入token中
String token = JwtTokenUtils.createToken(userDetails.getUsername(), role);
sysUser.setToken(JwtTokenUtils.TOKEN_PREFIX+token);
sysUser.setAuthorities(list);
SysUserVO userVO = ConvertUtils.sourceToTarget(sysUser, SysUserVO.class);
//返回json数据
JsonResult result = ResultTool.success();
result.setData(userVO);
//处理编码方式,防止中文乱码的情况
httpServletResponse.setContentType("text/json;charset=utf-8");
//塞到HttpServletResponse中返回给前台
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
- 访问决策管理器,CustomizeAccessDecisionManager
原来的代码暂无改变
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = collection.iterator();
//当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//当前请求需要的权限
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
//抛出AccessDeniedException异常,下面会新建个方法去捕获异常
throw new AccessDeniedException("权限不足!");
}
- 新添加AccessDeniedException权限异常捕获,CustomAccessDeniedHandler
/**
* @description: 捕获 AccessDeniedException异常
* @author: ※狗尾巴草
* @date: 2020-11-22 0:15
**/
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
JsonResult fail = ResultTool.fail(ResultCode.USER_NOT_LOGIN);
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(JSON.toJSONString(fail));
}
}
- 关于JWT
改变
删除原有的
// 如果请求头中没有Authorization信息则直接放行了 if (tokenHeader == null || !tokenHeader.startsWith(TestJwtUtils.TOKEN_PREFIX)) { chain.doFilter(request, response); return; }
这样就可以每个接口都取读取token,
修改getAuthentication方法,在getAuthentication方法解析token,取出权限或角色等信息
/**
* @description: 鉴权操作
* @author: ※狗尾巴草
* @date: 2020-11-14 0:20
**/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 对请求头d的token,进行解析,获取用户权限
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
super.doFilterInternal(request, response, chain);
}
// 从token中获取用户权限
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
// token为空或不正确,解析异常
try {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
String username = JwtTokenUtils.getUsername(token);
String roles = JwtTokenUtils.getUserRole(token);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for(String role: roles.split(",")){
if(StringUtils.isNotBlank(role)){
grantedAuthorities.add(new SimpleGrantedAuthority(role));
}
}
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null,grantedAuthorities);
}
}catch (Exception e){
return null;
}
return null;
}
}
- 关于配置
改变:
注释sesison
//.maximumSessions(1)//同一账号同时登录最大用户数
//.expiredSessionStrategy(sessionInformationExpiredStrategy)//会话信息过期策略会话信息过期策略(账号被挤下线)
添加禁用session代码
// 不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
添加拦截器
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
注释掉会话信息过期策略CustomizeSessionInformationExpiredStrategy这个类
完整配置代码
/**
* @description: Security配置
* @author: ※狗尾巴草
* @date: 2020-11-12 12:25
**/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomizeAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private CustomizeAuthenticationSuccessHandler successHandler;
@Autowired
private CustomizeAuthenticationFailureHandler failureHandler;
@Autowired
private CustomizeLogoutSuccessHandler logoutSuccessHandler;
// @Autowired
// private CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
@Autowired
private CustomizeAccessDecisionManager accessDecisionManager;
@Autowired
private CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
CustomAccessDeniedHandler accessDeniedHandler;
// @Autowired
// private CustomizeAbstractSecurityInterceptor securityInterceptor;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
// 设置默认的加密方式(强hash方式加密)
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
//获取用户账号密码及权限信息
return new UserDetailsServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置认证方式
auth.userDetailsService(userDetailsService());
}
// @Override
// public void configure(WebSecurity web) {
// //对于在header里面增加token等类似情况,放行所有OPTIONS请求。
// web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
// }
@Override
protected void configure(HttpSecurity http) throws Exception {
//http相关的配置,包括登入登出、异常处理、会话管理等
http
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);//访问决策管理器
o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
return o;
}
})
// .antMatchers("/user/**").hasAuthority("query_user")
//基于角色控制
// .antMatchers("/admin/**").hasAnyRole("admin")
// .antMatchers("/user/**").hasAnyRole("user")
//登入
.and()
.formLogin()
.loginPage("/auth/login")
.permitAll()//允许所有用户
.successHandler(successHandler)//登录成功处理逻辑
.failureHandler(failureHandler)//登录失败处理逻辑
// //登出
.and().logout()
.permitAll()//允许所有用户
.logoutSuccessHandler(logoutSuccessHandler)//登出成功处理逻辑
.deleteCookies("JSESSIONID")//登出之后删除cookie
//异常处理(权限拒绝、登录失效等)
.and().exceptionHandling().
authenticationEntryPoint(authenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
.accessDeniedHandler(accessDeniedHandler)//异常捕获
// .authenticationEntryPoint(new JWTAuthenticationEntryPoint())
// 限制同一账号只能一个用户使用 会话管理
.and().sessionManagement()
// .maximumSessions(1)//同一账号同时登录最大用户数
// .expiredSessionStrategy(sessionInformationExpiredStrategy)//会话信息过期策略会话信息过期策略(账号被挤下线)
;
http.csrf().disable().cors()
.and()
// .addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// 不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
// http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);//增加到默认拦截链中
}
}
-
添加两个用户
-
测试
一:未登录状态a.管理员资源接口:http://localhost:8666/admin/info
其他接口一样…
二:登录获取token
a.登录 localhost:8666/auth/login?username=user&password=123456
b 已经点击过登录,但不携带token进行访问,被拦截
以下携带token进行访问
c.访问user接口,http://localhost:8666/user/getUser,并成功返回数据
d.用普通用户的token访问管理员接口,http://localhost:8666/admin/info,并成功进行拦截
三:公共接口以及管理员接口同理,不做操作了
四:流程分析
1.携带token访问,就本项目而言,所写配置中,先被JWTAuthorizationFilter拦截器进行拦截,取出token中的权限信息,并返回UsernamePasswordAuthenticationToken对象,执行super.doFilterInternal(request, response, chain);
2 .查询接口所配置的权限,CustomizeFilterInvocationSecurityMetadataSource
3.然后到访问决策管理器CustomizeAccessDecisionManager ,对url所需要的权限和用户所具有的权限进行决策.具有权限就通过,权限不足便抛出异常
4.然后回到JWTAuthorizationFilter拦截器
省略一堆源码…最后访问到所需资源
二:基于角色控制
注:角色和权限共用GrantedAuthority接口,不同的是角色多了个前缀”ROLE_”
1.修改UserDetailsServiceImpl,登陆成功后,取出该用户所有的角色,并注释权限的相关代码
//获取用户所有角色
List<SysRoleEntity> roleList = sysRoleService.selectByUser(userEntity.getId());
// 声明用户角色
roleList.forEach(role ->{
//基于角色控制
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_"+role.getRole_code()));
});
2.SecurityConfig配置路由
//基于角色控制,这里的角色名不需要加前缀,因为在hasAnyRole方法的源码里面已经写好拼接了
.antMatchers("/admin/**").hasAnyRole("admin")
.antMatchers("/user/**").hasAnyRole("user")
3.注释权限配置代码,否则还是会以权限控制方式进行鉴权
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);//访问决策管理器
o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
return o;
}
})
a.进行登录,localhost:8666/auth/login?username=user&password=123456,获取token
携带token进行访问user接口,http://localhost:8666/user/getUser
携带token进行访问admin接口,http://localhost:8666/admin/info,并成功被拦截
注: 在参考的第一篇文章中,那个权限拦截器已经被我注销掉了,经过几次断点调试发现,他会在权限拦截器 和 访问决策管理器 循环三次,重复读取数据库数据,三次过后就正常访问资源,思来想去也想不明白,于是就被我删除了
最后,
项目地址
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/15428.html