Spring Security简介

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求。

Spring Security工作流程

1、登录的时候把,后台用一个类对象将用户信息封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。

2、执行接口的时候把有权限执行请求url的角色按照要求存起来。

3、对比 访问当前url所需要的权限和 当前用户的信息(权限),最后决定用户是否可以访问这个url。

数据库搭建

MySQL数据库。用户角色与权限管理细分为5个表——用户表、角色表、权限表、用户-角色表、角色-权限表。

springsecurity各版本区别 springsecurity详解_springsecurity各版本区别


springsecurity各版本区别 springsecurity详解_当前用户_02


springsecurity各版本区别 springsecurity详解_当前用户_03


springsecurity各版本区别 springsecurity详解_Spring Security_04


springsecurity各版本区别 springsecurity详解_当前用户_05

工具包

<!--Security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--getter setter注解依赖-->
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
</dependency>

具体实现步骤

这里只写Spring Securtiy步骤,DAO、Service等业务逻辑步骤省略
1、实现UserDetails接口和UserDetailsService接口(存储用户信息)
在我们的程序中,必须要有一个类,实现UserDetailsService这个接口并且重写它的loadUserByUsername(String s)这个方法。另外也必须要有一个类,实现UserDetails接口并重写里面的getAuthorities()方法。

写一个类继承UserDetails接口,重写里面getAuthorities()方法,这里用到了@Data 注解免去了手动加getter setter

@Data //getter setter注解
	public class Suser implements UserDetails {
	 
	    private String username;
	    private String password;
	    //包含着用户对应的所有Role,在使用时调用者给对象注入roles
	    private List<Srole> srole;
	    
	    //重写getAuthorities方法
	    //返回用户所有角色的封装,一个Srole对应一个GrantedAuthority
	    @Override
	    public Collection<? extends GrantedAuthority> getAuthorities() {
	        List<GrantedAuthority> authorities = new ArrayList<>();
	        for (Srole role : srole) {
	            authorities.add(new SimpleGrantedAuthority(role.getName()));
	        }
	        return authorities;
	    }
	
	    @Override
	    public String getUsername() {
	        return null;
	    }
	
	    @Override
	    public boolean isAccountNonExpired() {
	        return false;
	    }
	
	    @Override
	    public boolean isAccountNonLocked() {
	        return false;
	    }
	
	    @Override
	    public boolean isCredentialsNonExpired() {
	        return false;
	    }
	
	    @Override
	    public boolean isEnabled() {
	        return false;
	    }
    }

写一个类继承UserDetailsService接口,重写loadUserByUsername(String s)方法,我们在写UserService时直接实现这个接口就可以。所以UserService跟其他Service有些不同。

@Service
public class HrService implements UserDetailsService {

   @Autowired
   private SuserDao suserdao;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Suser su = suserdao.UserByUsername(s);
        if (su == null) {
            throw new UsernameNotFoundException("用户名不对");
        }
        return su;
    	}
   }

2、实现FilterInvocationSecurityMetadataSource接口(把有权限执行请求url的角色按照要求存起来)
作用是在用户请求一个地址的时候,截获这个地址,告诉程序访问这个地址需要哪些权限角色。

@Component
public class CustomMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    SpermissionService spermissionservice;//权限service
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) {
        //得到用户的请求地址,控制台输出一下
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        System.out.println("用户请求的地址是:" + requestUrl);

        //将resource所需要到的role按框架要求封装返回(MenuService里面的查询出所有的权限)
        List<Spermission> permission= spermissionservice.getAllMenu();
        for (Spermission spn : permission) {
            if (antPathMatcher.match(spn.getPage(), requestUrl)
                    &&spn.getSrole().size()>0) {
                List<Srole> roles = spn.getRoles();
                int size = roles.size();
                String[] values = new String[size];
                for (int i = 0; i < size; i++) {
                    values[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(values);
            }
        }
        //没有匹配上的资源,都是登录访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
    @Override
    public boolean supports(Class<?> aClass) {
        return FilterInvocation.class.isAssignableFrom(aClass);
    }
}

3、实现AccessDecisionManager接口(对比当前用户角色和访问URL所需要的角色,决定是否有权访问)

@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {
    @Override
    //第一个参数是当前登录用户的信息(角色),第三个参数是可以访问该url的角色
    public void decide(Authentication auth, Object o, Collection<ConfigAttribute> cas){
        Iterator<ConfigAttribute> iterator = cas.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();
            //当前请求需要的权限
            String needRole = ca.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                if (auth instanceof AnonymousAuthenticationToken) {
                    throw new BadCredentialsException("未登录");
                } else
                    return;
            }
            //当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
            for (GrantedAuthority authority : authorities) {
            	//对比当前用户角色(权限)和 访问URL角色(权限)
                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;
    }
}

4、实现AccessDeniedHandler接口(作用是自定义403响应内容)

@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    //自定义403响应内容
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp,
                       AccessDeniedException e) throws IOException {
        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        resp.setContentType("application/json;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        RespBean error = RespBean.error("权限不足,请联系管理员!");
        out.write(new ObjectMapper().writeValueAsString(error));
        out.flush();
        out.close();
    }
}

5、继承WebSecurityConfigurerAdapter类(这是Spring Security的一个配置类)

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private UserService userService;

   //根据一个url请求,获得访问它所需要的roles权限
   @Autowired
   MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;

   //接收一个用户的信息和访问一个url所需要的权限,判断该用户是否可以访问
   @Autowired
   MyAccessDecisionManager myAccessDecisionManager;

   //403页面
   @Autowired
   MyAccessDeniedHandler myAccessDeniedHandler;

   /**定义认证用户信息获取来源,密码校验规则等*/
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       /**有以下几种形式,使用第3种*/
       //inMemoryAuthentication 从内存中获取
       //auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123123")).roles("USER");

       //jdbcAuthentication从数据库中获取,但是默认是以security提供的表结构
       //usersByUsernameQuery 指定查询用户SQL
       //authoritiesByUsernameQuery 指定查询权限SQL
       //auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);

       //注入userDetailsService,需要实现userDetailsService接口
       auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
   }

   //在这里配置哪些页面不需要认证
   @Override
   public void configure(WebSecurity web) throws Exception {
       web.ignoring().antMatchers("/", "/noAuthenticate");
   }

   /**定义安全策略*/
   @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(myFilterInvocationSecurityMetadataSource);
                       o.setAccessDecisionManager(myAccessDecisionManager);
                       return o;
                   }
               })
//                .antMatchers("/hello").hasAuthority("ADMIN")
                .and()
                .formLogin()
                .loginPage("/login")
                .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\":"  + "}";
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                .and()
                .logout()
                .permitAll()
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);
    }
 
}