SpringSecurity使用@PreAuthorize、@PostAuthorize实现Web系统权限认证

SpringSecurity可以使用@PreAuthorize和@PostAuthorize进行访问控制,下面进行说明。
项目使用Springboot2.3.3+SpringSecurtiy+Mbatis实现。
首先先引入pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- 需要单独添加thymeleaf的布局模块 -->
        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>

项目中使用log4j2替代了logback日志系统,需要将log4j2的配置文件进行配置。

第一步:实现SpringSecurity的配置

扩展WebSecurityConfigurerAdapter的配置类

/**
 * @author MaLei
 * @description: 新建一个WebSecurityConfig类,使其继 承WebSecurityConfigurerAdapter
 * 在给WebSecutiryConfig类中加上@EnableWebSecurity 注解后,便会自动被 Spring发现并注册(查看
 * @EnableWebSecurity 即可看到@Configuration 注解已经存在
 * @create 2020/7/14
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启全局方法配置这个注解必须开启否则@PreAuthorize等注解不生效
public class WebSecutiryConfig extends WebSecurityConfigurerAdapter {
    //认证管理器配置方法可以配置定定义的UserDetailService和passwordEncoder。无需配置springboot2.3会自动注入bean
   /* @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(UserDetailService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }*/

    //核心过滤器配置方法
    //void configure(WebSecurity web)用来配置 WebSecurity。而 WebSecurity是基于 Servlet Filter用来配置 springSecurityFilterChain。而 springSecurityFilterChain又被委托给了 Spring Security 核心过滤器 Bean DelegatingFilterProxy。  相关逻辑你可以在 WebSecurityConfiguration中找到。一般不会过多来自定义 WebSecurity, 使用较多的使其ignoring()方法用来忽略Spring Security对静态资源的控制.对于静态资源的忽略尽量在此处设置,否则容易无限循环重新定向到登录页面
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/**", "/mylogin.html","/admin", "/favicon.ico");
}

    //安全过滤器链配置方法
    //void configure(HttpSecurity http)这个是我们使用最多的,用来配置 HttpSecurity。 HttpSecurity用于构建一个安全过滤器链 SecurityFilterChain。SecurityFilterChain最终被注入核心过滤器 。 HttpSecurity有许多我们需要的配置。我们可以通过它来进行自定义安全访问策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       // super.configure(http); 不能使用默认的验证方式
        //authorizeRequests()方法实际上返回了一个 URL 拦截注册器,我们可以调用它提供的
        //anyanyRequest()、antMatchers()和regexMatchers()等方法来匹配系统的URL,并为其指定安全
        //策略
       http.authorizeRequests()
                .anyRequest().authenticated() 
                .and()
                //formLogin()方法和httpBasic()方法都声明了需要Spring Security提供的表单认证方式,分别返
                //回对应的配置器
                .formLogin()
                //,formLogin().loginPage("/myLogin.html")指定自定义的登录
                ///myLogin.html,同时,Spring Security会用/myLogin.html注册一个POST路由,用于接收登录请求
               //loginProcessingUrl("/login")指定的/login必须与表单提交中指向的action一致
                   .loginPage("/mylogin.html").loginProcessingUrl("/logins").permitAll()
               //表单中用户名和密码对应参数设置(默认为username和password),如果是默认值则不用设置下面的参数对应.
               .usernameParameter("usernames").passwordParameter("passwords")
               .successForwardUrl("/hello")
               .failureHandler(new AuthenticationFailureHandler() {
                   @Override
                   public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                       httpServletResponse.setContentType("application/json;charset=UTF-8");
                       httpServletResponse.setStatus(403);
                       String error=new String();
                       if (e instanceof BadCredentialsException ||
                               e instanceof UsernameNotFoundException) {
                           error="账户名或者密码输入错误!";
                       } else if (e instanceof LockedException) {
                           error="账户被锁定,请联系管理员!";
                       } else if (e instanceof CredentialsExpiredException) {
                           error="密码过期,请联系管理员!";
                       } else if (e instanceof AccountExpiredException) {
                           error="账户过期,请联系管理员!";
                       } else if (e instanceof DisabledException) {
                           error="账户被禁用,请联系管理员!";
                       } else {
                           error="登录失败!";
                       }
                       httpServletResponse.getWriter().write("{\"message\":\""+error+"\"}");
                   }
               })
               .and()
               .logout()
               .logoutUrl("/logout")
               .logoutSuccessHandler(new LogoutSuccessHandler() {
                   @Override
                   public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException {
                       resp.setContentType("application/json;charset=utf-8");

                      @Cleanup PrintWriter out = resp.getWriter();
                       out.write("{\"msg\":\"注销成功!\"}");
                       out.flush();
                      // out.close();
                   }
               })
               .permitAll()
                .and()
                //csrf()方法是Spring Security提供的跨站请求伪造防护功能,当我们继承WebSecurityConfigurer
                //Adapter时会默认开启csrf()方法
                .csrf().disable()
               //只有确实的访问失败才会进入AccessDeniedHandler,如果是未登陆或者会话超时等,不会触发AccessDeniedHandler,而是会直接跳转到登陆页面
       .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
           @Override
           public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
               httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
               httpServletResponse.setContentType("application/json;charset=UTF-8");
               PrintWriter out = httpServletResponse.getWriter();
               out.write(new ObjectMapper().writeValueAsString("{\"message\":\"权限不足,请联系管理员!\"}"));
               out.flush();
               out.close();
           }
       });

    }
    /**
     * 增加密码加密器,一旦增加,在验证过程中security将使用密码加密器进行加密对比,数据库中如果存储明文密码,在
     * UserDetailsService接口实现方法中,先加密密码然后才能返回UserDetails
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder(){
        //使用系统自带密码加密器也可以参考上一篇自己继承PasswordEncoder接口写编码器
        return new BCryptPasswordEncoder();
    }
}

在配置文件中,静态资源、登录页面等求情不需要登录账户即可访问,我放在了public void configure(WebSecurity web) throws Exception方法中。

第二步:实现UserDetails及UserDetailsService接口。

User类实现UserDetails接口

public class User implements Serializable, UserDetails {
    private Long id;

    private String username;

    private String password;

    private String name;

    private Boolean enabled;

    private List<Role> roles;

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    private static final long serialVersionUID = 1L;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

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

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

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

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

    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }
    //将当前账户的所属角色进行配置
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> list=new ArrayList<>();
        //将当前用户的配属角色填入集合
        Assert.notNull(roles,"角色集合为null");
        for (Role r:roles){
            list.add(new SimpleGrantedAuthority(r.getName()));
        }
        return list.size()>0?list:null;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", username=").append(username);
        sb.append(", password=").append(password);
        sb.append(", name=").append(name);
        sb.append(", enabled=").append(enabled);
        sb.append("]");
        return sb.toString();
    }
}

UserDetailService类实现UserDetailsService接口

/**
 * @author MaLei
 * @description: UserDetailService
 * @create 2020/7/14
 */
@Component
@Slf4j
public class UserDetailService implements UserDetailsService {
    @Autowired
    UserMapper customerMapper;
    @Override
    @Transactional("firstTransactionManager")

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录账号:{}",username);
        //数据库读取账户,如果读出来的密码是明码必须使用PasswordEncoder加密
        User cust=customerMapper.selectByUserNameContainsRoles(username);
        if(cust==null)
            throw new UsernameNotFoundException("账户不存在");
        return cust;
    }
}

由于使用Mybatis实现的数据库操作,具体的mapper实现就不贴出来了。

第三步在前段Controller的方法上使用@PreAuthorize和@PostAuthorize
@PreAuthorize是在方法执行前进行权限认证
@PostAuthorize是方法执行后在返回前进行权限认证

@RestController
public class TestController {
    @RequestMapping("/hello")
    @PreAuthorize("hasPermission('/hello', 'read') or hasRole('ROLE_admin')")
    public String hello() {
        return  "hello";
}

代码@PreAuthorize(“hasPermission(’/hello’, ‘read’) or hasRole(‘ROLE_admin’)”)中也使用了hasRole(‘ROLE_admin’),这代表当前登录用户只要具有ROLE_admin这个角色,即可访问。hasRole(‘ROLE_admin’)中角色名称可以写成ROLE_admin也可简写admin,系统会自动判断是否有ROLE_前缀,没有会自动加上。
hasPermission(’/hello’, ‘read’)系统不会自动处理,需要实现PermissionEvaluator接口进行处理。

第四步骤:实现PermissionEvaluator接口

/**
 * @author MaLei
 * @description: PermissionEvaluator接口实现类
 * @create 2020/7/17
 */
@Configuration
public class MyPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    MenuMapper menuMapper;
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        boolean accessable = false;
        if(authentication.getPrincipal().toString().compareToIgnoreCase("anonymousUser") != 0){
            Menu menu= menuMapper.selectByRequestUrl(targetDomainObject.toString());
            if(menu==null) return accessable;
            List<Role> roles = menu.getRoles();
            Iterator<Role> it=roles.iterator();
            while(it.hasNext()) {
                Role role=it.next();
                for (GrantedAuthority authority : authentication.getAuthorities()) {
                   if(role.getName().equals(authority.getAuthority())){
                       accessable=true;
                   }
                }
            }
        }
        return accessable;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission)方法是处理
@PreAuthorize(“hasPermission(’/hello’, ‘read’) or hasRole(‘ROLE_admin’)”)
注解中hasPermission(’/hello’, ‘read’) 传入的信息的,
hasPermission方法中Authentication authentication参数代表登录用户及权限信息
Object targetDomainObject参数代表hasPermission(’/hello’, ‘read’) 中第一个参数"/hello"
Object permission参数代表hasPermission(’/hello’, ‘read’)中第二个参数"read"

在高版本的springboot中,实现了PermissionEvaluator接口后,只要将实现类加上@Configuration系统会自动进行注册到容器。

如果低版本springboot中,自定义的PermissionEvaluator实现类后不生效,需要在自己实现的WebSecutiryConfig配置类中声明一个@Bean,形式如下:

/**
     * 注入自定义PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        //将自己实现的PermissionEvaluator接口实现类加入处理器
        handler.setPermissionEvaluator(new MyPermissionEvaluator());
        return handler;
    }

通过以上配置就实现了使用@PreAuthorize、@PostAuthorize等注解来进行权限限制,本例中设置了所有请求必须登录后才能访问,如果一个账户登录了,但是请求某一资源后,该资源方法上未加上@PreAuthorize或@PostAuthorize注解,则代表该资源无需访问权限,可以直接访问,这点要注意。

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了若依管理系统。她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。所有前端后台代码封装过后十分精简易上手,出错效率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。 您是否在找一套合适后台管理系统。 您是否在找一套代码易读易懂后台管理系统。 那么,现在若依来了。诚意奉献之作 若依是给刚出生的女儿取的名字 寓意:你若不离不弃,我必生死相依 内置功能 用户管理:用户是系统操作者。 部门管理:配置系统组织机构。 岗位管理:岗位是用户所属职务。 菜单管理:配置系统菜单(支持控制到按钮)。 角色管理:角色菜单权限分配。 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 操作日志:系统操作日志记录(含异常)。 登录日志:系统登录情况记录(含异常)。 在线用户:当前系统中活跃用户状态监控。 连接池监视:监视当期系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 为何选择若依 是一个完全响应式,基于Bootstrap3.3.6最新版本开发的主题。 她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。 拥有良好的代码结构,层次结构清晰。内置一系列基础功能。 操作权限控制精密细致,对所有管理链接都进行权限验证,可控制到按钮。 提供在线功能代码生成工具,提高开发效率及质量。 提供常用工具类封装,日志、国际化、缓存、验证、字典等数据。 兼容目前最流行浏览器(IE7+、Chrome、Firefox)手机移动端也支持。 技术选型 1、后端 核心框架:Spring Boot 安全框架:Apache Shiro 模板引擎:Thymeleaf 持久层框架:MyBatis 数据库连接池:Druid 缓存框架:Ehcache 日志管理:SLF4J 工具类:Apache Commons Fastjson POJO:Lombok 2、前端 框架:Bootstrap 数据表格:Bootstrap Table 客户端验证:JQuery Validation 树结构控件:zTree 弹出层:layer 3、平台 服务器中间件:SpringBoot内置 数据库支持:目前仅提供MySql数据库的支持,但不限于数据库 开发环境:Java、Eclipse、Maven、Git

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值