SpringBoot整合Spring Secruity的基本用法和数据库动态权限配置

一个Java菜鸟的修炼记录 个人博客 youngljx.top

在这里插入图片描述

SpringBoot 安全管理之 Spring Security

基于SpringBoot的自动化配置安全管理使用Spring Security比Shiro更适用

基本配置

1.基本用法,引入依赖,项目中的所有资源会默认的被保护起来

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

2.简单地配置用户名和密码,不会在在生成随机的密码,示例

spring.security.user.name=ljx
spring.security.user.password=123
spring.security.user.roles=admin

3.基于内存的认证,简单示例

   @Bean
    PasswordEncoder passwordEncoder() {
        return new NoOpPasswordEncoder().getInstance;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()            
               .withUser("ljx")      
               .password("123")
               .roles("admin")
               .and()
               .withUser("dage")
               .password("123")
               .roles("user");
    }

登录表单,注销登录,密码加密的详细配置见下文

基于数据库的认证,动态权限配置

1.数据库表模型如下

值得注意的是,数据库表role的字段前要加ROLE_,如果不加如下修改

用户实体类实现UserDetails接口,重写其中的方法

@Data
public class User implements UserDetails {

    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<Role> roles;

   @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
                                            //"ROLE_"+role.getName()
           authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public String getPassword() {
        return password;
    }       
}    

定义UserService实现UserServiceDetails

@Service
public class UserService implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("账户不存在!");
        }
        user.setRoles(userMapper.getUserRolesByUid(user.getId()));
        return user;
    }
}

2.自定义 FilterInvocationSecurityMetadataSource确定一个请求需要哪些角色,代码如下:

   @Component
   public class MyFilter implements FilterInvocationSecurityMetadataSource {
   
       /**
       * @Description:  定义路径匹配符
       */
       AntPathMatcher pathMatcher=new AntPathMatcher();
   
       @Autowired
       MenuService menuService;
   
       /**
       * @Description:  动态匹配请求url的角色权限
       * @Param: [o] 
       * @return: java.util.Collection<org.springframework.security.access.ConfigAttribute> 
       */
       @Override
       public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
           //获取请求url地址
           String requestUrl = ((FilterInvocation) o).getRequestUrl();
           //获取menu中的设置的pattern以及角色role的集合
           List<Menu> allMenus = menuService.getAllMenus();
           //遍历比较pattern和url
           for (Menu menu : allMenus) {
               if (pathMatcher.match(menu.getPattern(),requestUrl)){
                   List<Role> roles = menu.getRoles();
                   String[] rolesStr=new String[roles.size()];
                   for (int i = 0; i < rolesStr.length; i++) {
                       rolesStr[i]=roles.get(i).getName();
                   }
                   return SecurityConfig.createList(rolesStr);
               }
           }
           //路径都不匹配,设置默认的返回值
           return SecurityConfig.createList("ROLE_login");
       }
   
       @Override
       public Collection<ConfigAttribute> getAllConfigAttributes() {
           return null;
       }
   
       @Override
       public boolean supports(Class<?> clazz) {
           return FilterInvocation.class.isAssignableForm(clazz);
       }
   }

3.自定义AccessDecisionManager进行角色对比,代码如下:

      @Component
      public class MyAccessDecisionManager implements AccessDecisionManager {
          @Override
          public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection)
                  throws AccessDeniedException, InsufficientAuthenticationException {
              for (ConfigAttribute attribute : collection) {
                  if ("ROLE_login".equals(attribute.getAttribute())){
                      if (authentication instanceof AnonymousAuthenticationToken) {
                          throw new AccessDeniedException("权限不足!");
                      }else {
                          return;
                      }
                  }
                  Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                  for (GrantedAuthority authority : authorities) {
                      if (authority.getAuthority().equals(attribute.getAttribute())){
                          return;
                      }
                  }
              }
              throw new AccessDeniedException("权限不足!");
          }
      
          @Override
          public boolean supports(ConfigAttribute configAttribute) {
              return true;
          }
      
          @Override
          public boolean supports(Class<?> aClass) {
              return true;
          }
      }

涉及到的MenuMapper.xml的配置如下

  <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.ljx.securitydb.mapper.MenuMapper">
      
          <!--resultMap将statement sql查询结果映射为java对象-->
           <resultMap type="com.ljx.securitydb.bean.Menu" id="MenuMap">
             <result property="id" column="id" />
             <result property="pattern" column="pattern" />
             <collection property="roles" ofType="com.ljx.securitydb.bean.Role">
                 <id property="id" column="rid"/>
                 <result property="name" column="rname"/>
                 <result property="nameZh" column="rnameZh"/>
             </collection>
         </resultMap>
      
          <!--多表关联查询-->
          <select id="getAllMenus" resultMap="MenuMap">
              select m.*,r.id as rid,r.name as rname,r.nameZh as rnameZh from menu m
                  left join menu_rloe mr on m.id=mr.mid
                  left join role r on mr.rid=r.id
          </select>
      </mapper>

统一结果响应类如下:

public class RespBean {
    private Integer status;
    private String msg;
    private Object object;

    public static RespBean ok(String msg){
        return new RespBean(200,msg,null);
    }

    public static RespBean ok(String msg,Object object){
        return new RespBean(200,msg,object);
    }

    public static RespBean error(String msg){
        return new RespBean(500,msg,null);
    }

    public static RespBean error(String msg,Object object){
        return new RespBean(500,msg,object);
    }

    private RespBean(){}

    private RespBean(Integer status, String msg, Object object) {
        this.status = status;
        this.msg = msg;
        this.object = object;
    }
  //省略getter和setter
}

4.SecruityConfig 的配置如下

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserService userService;
         
    @Autowired
    MyFilter myFilter;
         
    @Autowired
    MyAccessDecisionManager myAccessDecisionManager;
         
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth.userDetailsService(userService);
   }

    @Bean
    PasswordEncoder passwordEncoder(){
          return new BCryptPasswordEncoder();
    }


    @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(myFilter);
                        o.setAccessDecisionManager(myAccessDecisionManager);
                        return o;
                    }
                })
                .and()
               //登录表单配置
                .formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/doLogin")
                .loginPage("/login")
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        //获取登录成功的用户对象并返回统一响应对象
                        Hr hr = (Hr) authentication.getPrincipal();
                        hr.setPassword(null);
                        RespBean ok = RespBean.ok("登录成功", hr);
                        String s = new ObjectMapper().writeValueAsString(ok);
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        response.setContentType("application/json ; charset=utf-8");
                        PrintWriter out = response.getWriter();
                        RespBean respBean = RespBean.error("登录失败");
                        if (e instanceof LockedException){
                            respBean.setMsg("账户被锁定,请联系管理员!");
                        }else if (e instanceof CredentialsExpiredException){
                            respBean.setMsg("密码过期,请联系管理员!");
                        }else if (e instanceof AccountExpiredException) {
                            respBean.setMsg("账户过期,请联系管理员!");
                        }else if (e instanceof DisabledException){
                            respBean.setMsg("账户被禁用,请联系管理员!");
                        }else if (e instanceof BadCredentialsException){
                            respBean.setMsg("账户名或密码错误,请重新输入!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(respBean));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                //注销登录配置
                .logout()
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功!")));
                        out.flush();
                        out.close();
                    }
                })
                .and()
                //测试关闭crsf
                .csrf()
                .disable();
             }
         }

基于上面的配置已经实现了动态权限配置,权限和资源的关系就可以在menu_role表中动态调整

记录学习中的知识点,方便加深理解

未完待续,OAuth2

posted @ 2020-06-04 23:06  young帆起航  阅读(583)  评论(0)    收藏  举报