spring security 自定义
简介
狂神spring security教程没有自定义权限拦截,本文根据[vhr项目](lenve/vhr: 微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。 (github.com))来学习一些自定义的处理。
主要流程
定义entity类
-
UserDetails接口
-
userDetails => Spring Security基础接口,包含某个用户的账号,密码,权限,状态(是否锁定)等信息。只有getter方法。 相当于定义一个规范,作用主要是用来和数据库做交互用的。简单来说,就是用户名传过来,这个类负责校验用户名是否存在业务逻辑。Security这个框架不管你的应用时怎么存储用户和权限信息的。只要你取出来的时候把它包装成一个UserDetails对象给我用就可以了。
-
源码
-
package org.springframework.security.core.userdetails; import java.io.Serializable; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; public interface UserDetails extends Serializable { //返回用户的所有角色 Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); //账户是否未过期 boolean isAccountNonExpired(); //账户是否未锁定 boolean isAccountNonLocked(); //凭证是否未过期 boolean isCredentialsNonExpired(); //账户是否可用 boolean isEnabled(); }
-
-
-
因此我们定义的entity类要继承UserDetails,使得Spring Security与数据库交互
-
其中主要重写getAuthorities方法,我们从数据库中读取角色,返回给spring security
-
例程
-
@Data public class Hr implements UserDetails { private Integer id; private String name; private String phone; private String telephone; private String address; private Boolean enabled; private String username; private String password; private String userface; private String remark; private List<Role> roles; @Override @JsonIgnore public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(roles.size()); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
向spring security提供相关信息
-
其实认证的操作,框架都已经帮你实现了,它所需要的获取信息的方式。所以它就定义一个接口,然后让你去实现,实现好了之后再注入给它。
-
框架提供一个UserDetailsService接口用来加载用户信息。UserDetailsService里面只有一个方法,作用就是通过username查询用户的信息。
-
package org.springframework.security.core.userdetails; public interface UserDetailsService { UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException; } -
public interface HrService extends UserDetailsService { public List<Hr> getAllHrs(String keywords); public Integer updateHr(Hr hr); public boolean updateHrRole(Integer hrid, Integer[] rids); public Integer deleteHrById(Integer id); public List<Hr> getAllHrsExceptCurrentHr(); public Integer updateHyById(Hr hr); public boolean updateHrPasswd(String oldpass, String pass, Integer hrid); public Integer updateUserface(String url, Integer id); } -
@Service public class HrServiceImpl implements HrService { @Autowired HrMapper hrMapper; @Autowired HrRoleMapper hrRoleMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Hr hr = hrMapper.loadUserByUsername(username); if (hr == null) { throw new UsernameNotFoundException("用户名不存在!"); } hr.setRoles(hrMapper.getHrRolesById(hr.getId())); return hr; } public List<Hr> getAllHrs(String keywords) { return hrMapper.getAllHrs(HrUtils.getCurrentHr().getId(),keywords); } public Integer updateHr(Hr hr) { return hrMapper.updateByPrimaryKeySelective(hr); } @Transactional public boolean updateHrRole(Integer hrid, Integer[] rids) { hrRoleMapper.deleteByHrid(hrid); return hrRoleMapper.addRole(hrid, rids) == rids.length; } public Integer deleteHrById(Integer id) { return hrMapper.deleteByPrimaryKey(id); } public List<Hr> getAllHrsExceptCurrentHr() { return hrMapper.getAllHrsExceptCurrentHr(HrUtils.getCurrentHr().getId()); } public Integer updateHyById(Hr hr) { return hrMapper.updateByPrimaryKeySelective(hr); } public boolean updateHrPasswd(String oldpass, String pass, Integer hrid) { Hr hr = hrMapper.selectByPrimaryKey(hrid); BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); if (encoder.matches(oldpass, hr.getPassword())) { String encodePass = encoder.encode(pass); Integer result = hrMapper.updatePasswd(hrid, encodePass); if (result == 1) { return true; } } return false; } public Integer updateUserface(String url, Integer id) { return hrMapper.updateUserface(url, id); } } -
再把UserDetailsService提供给spring security(同时对密码加密)
-
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(hrService).passwordEncoder(new BCryptPasswordEncoder()); } -
现在就可以读取数据库中相关的信息
自定义权限拦截
获取URL资源需要的角色
-
自定义FilterInvocationSecurityMetadataSource
-
FilterInvocationSecurityMetadataSource 有一个默认的实现类DefaultFilterInvocationSecurityMetadataSource,该类的主要功能就是通过当前的请求地址,获取该地址需要的用户角色,照猫画虎需要自定义FilterInvocationSecurityMetadataSource
-
@Component public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { //资源表 @Autowired MenuService menuService; AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { // 获取当前请求url String requestUrl = ((FilterInvocation) object).getRequestUrl(); // TODO 忽略url请放在此处进行过滤放行 if ("/login".equals(requestUrl)) { return null; } // 数据库中所有url java.util.List<Menu> menus = menuService.getAllMenusWithRole(); for (Menu menu : menus) { // 获取该url所对应的权限 //使用antPathMatcher进行路径匹配 if (antPathMatcher.match(menu.getUrl(), requestUrl)) { List<Role> roles = menu.getRoles(); String[] str = new String[roles.size()]; for (int i = 0; i < roles.size(); i++) { str[i] = roles.get(i).getName(); } // 保存该url对应角色权限信息 return SecurityConfig.createList(str); } } //ROLE_LOGIN在角色数据库中并不存在,后面单独处理 return SecurityConfig.createList("ROLE_LOGIN"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } }
-
验证用户的权限
-
自定义AccessDecisionManager来验证用户的权限
-
@Component public class UrlAccessDecisionManager implements AccessDecisionManager { /** * * @param authentication 保存了当前登录用户的角色信息 * @param o * @param collection 当前请求需要的角色(可能有多个) * @throws AccessDeniedException * @throws AuthenticationException */ @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, AuthenticationException { Iterator<ConfigAttribute> iterator = collection.iterator(); while (iterator.hasNext()) { ConfigAttribute ca = iterator.next(); //当前请求需要的权限 String needRole = ca.getAttribute(); //ROLE_LOGIN单独处理 if ("ROLE_LOGIN".equals(needRole)) { if (authentication instanceof AnonymousAuthenticationToken) { //重新登录,给AuthenticationEntryPoint处理 throw new BadCredentialsException("未登录"); } else return; } //当前用户所具有的权限 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (GrantedAuthority authority : authorities) { if (authority.getAuthority().equals(needRole)) { return; } } } throw new AccessDeniedException("权限不足!"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
自定义权限不足处理
-
@Component public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler { //响应403错误 @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException { resp.setStatus(HttpServletResponse.SC_FORBIDDEN); resp.setCharacterEncoding("UTF-8"); PrintWriter out = resp.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}"); out.flush(); out.close(); } } -
accessDeniedHandler:访问拒绝处理 就是你要访问某个资源,但是当你没有访问权限时,就会抛出异常,在此类中进行处理。
spring security配置
-
登录界面使用spring security自带的,当然可以自己定制,在formLogin里面配置
-
配置登录成功
-
配置登录失败
-
配置加密方式
-
配置UserDetailsService
-
配置自定义的AccessDecisionManager与FilterInvocationSecurityMetadataSource
-
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired HrService hrService; @Autowired UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource; @Autowired UrlAccessDecisionManager urlAccessDecisionManager; @Autowired AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler; // @Bean // public PasswordEncoder passwordEncoder() { // //为了演示方便,我们使用NoOpPasswordEncoder(这个就是不加密) // return NoOpPasswordEncoder.getInstance(); // } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(hrService).passwordEncoder(new BCryptPasswordEncoder()); } //配置资源放行,不登录就可以访问 @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/index.html", "/static/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource); o.setAccessDecisionManager(urlAccessDecisionManager); return o; } }).and().formLogin().usernameParameter("username").passwordParameter("password").permitAll().failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); StringBuffer sb = new StringBuffer(); sb.append("{\"status\":\"error\",\"msg\":\""); if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) { sb.append("用户名或密码输入错误,登录失败!"); } else if (e instanceof DisabledException) { sb.append("账户被禁用,登录失败,请联系管理员!"); } else { sb.append("登录失败!"); } sb.append("\"}"); out.write(sb.toString()); out.flush(); out.close(); } }).successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); ObjectMapper objectMapper = new ObjectMapper(); String s = "{\"status\":\"success\",\"msg\":" + objectMapper.writeValueAsString(HrUtils.getCurrentHr()) + "}"; out.write(s); out.flush(); out.close(); } }).and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler); } }

浙公网安备 33010602011771号