前面已经实现了登陆,即认证,下面是登陆之后的鉴权(即某些角色只能访问特定的资源)
/**
* @auther Mr.Liao
* @date 2019/8/20 20:24
*/
@Component
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private MenuMapper menuMapper;
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//获取请求的url
String requestUrl = ((FilterInvocation) object).getRequestUrl();
//获取当前请求需要的角色信息,拿url去menu表中匹配,查看是访问的那个menu下的资源,根据menuId 获取到role信息
List<Menu> menuList = menuMapper.findAllMenu();
for (Menu menu : menuList) {
//如果请求的路径包含在某个menu的url中,且能访问该资源的角色信息存在
if (antPathMatcher.match(menu.getUrl(),requestUrl) && menu.getRoles().size() > 0) {
List<Role> roles = menu.getRoles();
int size = roles.size();
//定义一个数组,来接收能访问该资源的角色
String[] roleNameArray = new String[size];
for (int i = 0; i < size; i++) {
roleNameArray[i] = roles.get(i).getRoleAuthority();
}
return SecurityConfig.createList(roleNameArray);
}
}
//如果遍历完menu之后没有匹配上,说名访问该资源不需要权限信息,设置一个登陆就能访问的角色
return SecurityConfig.createList("ROLE_login");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
/**
* @auther Mr.Liao
* @date 2019/8/20 21:18
*/
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()){
ConfigAttribute configAttribute = iterator.next();
//访问该请求url需要的角色信息
String needRole = configAttribute.getAttribute();
//如果只是登陆就能访问,即没有匹配到资源信息
if ("ROLE_login".equals(needRole)){
//判断是否登陆,没有登陆则authentication是AnonymousAuthenticationToken接口实现类的对象
if (authentication instanceof AnonymousAuthenticationToken){
throw new BadCredentialsException("未登录");
} else return;
}
//如果匹配上了资源信息,就拿登陆用户的权限信息来对比是否存在于已匹配的角色集合中
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)){
return;
}
}
}
//如果没有匹配上,则权限不足
throw new AccessDeniedException("权限不足");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
RespBean respBean = new RespBean(403, "权限不足", e.getMessage());
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
}
配置:
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserLoginService userLoginService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private SuccessHandler successHandler;
@Autowired
private FailureHandler failureHandler;
@Autowired
private ImageCodeFilter imageCodeFilter;
@Autowired
private DataSource dataSource;
@Autowired
private MySecurityMetadataSource mySecurityMetadataSource;
@Autowired
private MyAccessDecisionManager myAccessDecisionManager;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Bean//PersistentTokenRepository记住我功能的工具类
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userLoginService)
.passwordEncoder(bCryptPasswordEncoder);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers( "/login_p.html", "/authentication/request","/favicon.ico","/code/image");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(mySecurityMetadataSource);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
.and()
//表单登陆配置
.formLogin()
.loginPage("/authentication/request").loginProcessingUrl("/user/login")
.usernameParameter("username").passwordParameter("password")
.successHandler(successHandler).failureHandler(failureHandler)
.permitAll()
.and()
//记住我配置
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(20)
.userDetailsService(userLoginService)
.and()// 关闭跨站请求伪造防护
.csrf().disable()
.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
}
}
测试接口:
@RestController
public class TestController {
@GetMapping("/emp/basic/test")
public String test1(){
return "员工资料接口";
}
@GetMapping("/emp/recruitment/test")
public String test2(){
return "员工招聘接口";
}
@GetMapping("/salary/change/test")
public String test3(){
return "工资调整接口";
}
@GetMapping("/salary/statistics/test")
public String test4(){
return "工资统计接口";
}
@GetMapping("/per/evaluation/test")
public String test5(){
return "业绩考核接口";
}
@GetMapping("/per/statistics/test")
public String test6(){
return "业绩统计接口";
}
@GetMapping("/test")
public String test7(){
return "登陆就能访问的接口";
}
}
登陆张三
访问结果
修改用户的权限信息时,将用户和角色的id添加到user_role表中,调整角色能访问的资源信息时,将关联的角色和资源信息添加到role_menu表中