SpringBoot权限管理

1、SpringSecurity

简介

spring security 的核心功能主要包括:-  认证 (你是谁) -  授权 (你能干什么) -  攻击防护 (防止伪造身份)
     其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。


比如,对于username password认证过滤器来说,
    会检查是否是一个登录请求;
    是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;
    如果不满足则放行给下一个。
     下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。

下面结合具体的项目进行介绍,以下是项目的结构图:

下面是resource文件夹的详细内容


2、Pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入springsecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--加入thymeleaf对springsecurity的支持-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
 </dependencies>

在springboot中引入SpringSecurity的依赖只需要引入spring-boot-starter-security即可。

3、主要的html页面

1.登录页面login.html如下:

<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>SpringSecurity</title>
</head>
<body>
<h2>登录页面</h2>
<form action="/admin/login" method="post">
    用户名:<input type="text" name="user_name" value=""/><br/>
    密码:<input type="text" name="pass_word" value=""/><br/>
    记住我:<input type="checkbox" name="remember-me" value="true"> <br/>
    <button type="submit">登录</button>
</form>
</body>
</html>

2.登录成功之后的主页面index.html如下:

<!doctype html>
<html xmlns:th="http://www.w3.org/1999/xhtml"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录账号: <span sec:authentication="name">00</span> <br>
登录账号:<span sec:authentication="principal.username">11</span> <br>
凭证:<span sec:authentication="credentials">22</span> <br>
权限和角色:<span sec:authentication="authorities">33</span> <br>
客户端地址:<span sec:authentication="details.remoteAddress">44</span> <br>
sessionId:<span sec:authentication="details.sessionId">55</span> <br>
<hr/>
<br/>
通过权限判断:<br>
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button>
<br>
通过角色判断:<br>
<button sec:authorize="hasRole('syw')">新增</button>
<button sec:authorize="hasRole('syw')">删除</button>
<button sec:authorize="hasRole('lmy')">修改</button>
<button sec:authorize="hasRole('lmy')">查看</button>
<hr/>
<br>
首页 <a href="/admin/admin"> admin权限</a>
<a href="/admin/root"> root权限</a>
<a href="/admin/normal"> normal权限</a>
<br>
springsecurity默认的退出登录的路径为 /logout
<a href="/logout">退出登录</a>
</body>
</html>

3.自定义错误页面error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
自定义失败页面
</body>
</html>

其余的页面仅仅用来做跳转,这里就不详细介绍了。

4、启动类

如果想要使用角色,则需要在springboot的启动类中开启角色注解

@SpringBootApplication
@MapperScan("cn.njust.dao")
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)  //开启springsecurity中的@Secured角色注解,以及@PreAuthorize表达式进行角色管理。角色允许以ROLE_开头,当然也可以不加ROLE_
public class SpringbootsecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootsecurityApplication.class, args);
    }
}

prePostEnabled = true 会解锁 @PreAuthorize 和 @PostAuthorize 两个注解。从名字就可以看出@PreAuthorize 注解会在方法执行前进行验证,而 @PostAuthorize 注解会在方法执行后进行验证。

@Secured注解是用来定义业务方法的安全配置。在需要安全[角色/权限等]的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。

5、Controller层

@Controller
@RequestMapping("/admin")
public class LoginController {
    @RequestMapping("/login")
    public String login(){
        return "login";
    }
    @RequestMapping("/logout")
    public String logout(){
        return "login";
    }
    @RequestMapping("/index")
    public String index(){
        return "index";
    }
    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
    @RequestMapping("/error")
    public String error(){
        return "error";
    }
    @RequestMapping("/admin")
    public String admin(){
        return "admin";
    }
    //注意:这个注解一般写在controller层,注解中必须要有ROLE_
    //@Secured("ROLE_syw")  //角色注解,只有具有syw角色的才能访问该地址,相当于配置文件中添加.antMatchers("/admin/root").hasRole("syw")
    @PreAuthorize("hasRole('syw')")   //在方法执行之前进行判断,这里面放的是access权限表达式,这个hasRole里面可以写成ROLE_syw或者直接写syw,都可以
    //@PostAuthorize("hasRole('syw')") //在方法执行之后进行判断,不常用。
    @RequestMapping("/root")
    public String root(){
        return "root";
    }
    @RequestMapping("/normal")
    public String normal(){
        return "normal";
    }
}

controller层可以通过注解的方式设置对应链接请求的权限,

@Secured("ROLE_syw")  //角色注解,只有具有syw角色的才能访问该地址,相当于在springsecurity中添加.antMatchers("/admin/root").hasRole("syw")。

@PreAuthorize()  //该注解和上面一个注解的功能没什么区别,该注解是在方法执行前进行权限校验的。

@PostAuthorize() //该注解是在方法执行之后进行判断的,不常用。

6、Service层

注意:在Service层中需要实现springsecurity默认的方法,一般是在用户操作的service方法进行实现即可,因为权限校验验证的是用户,和其他的业务无关。

//这个抽象类是springsecurity默认写好的,我们只需要重写里面的方法即可
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UsersMapper usersMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("我经过了loadUserByUsername");
        UsersExample usersExample = new UsersExample();
        UsersExample.Criteria criteria = usersExample.createCriteria();
        criteria.andUsernameEqualTo(username);
        List<Users> users = usersMapper.selectByExample(usersExample);
        if(users==null||users.size()==0){
            throw new UsernameNotFoundException("用户名不存在");
        }
        System.out.println(users);
        //注意角色一定要以ROLE_ 开头
        //给该登录成功的用户加上权限和角色,这里是自己定义的三种权限类型,root ,admin ,normal ,这里的权限要和MySecurityConfig中的hasAuthority()中的对应上
        List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList("root", "admin", "normal","ROLE_syw","/update","/delete");
        //AuthorityUtils.commaSeparatedStringToAuthorityList("root,admin,normal,ROLE_syw")也可以,这个可以连一块写,用逗号分割,上面那个是分开写
        return new User(username,passwordEncoder.encode(users.get(0).getPassword()),authorityList);
    }

为了方便演示,这里我是手动添加的权限,正常情况下权限应该是存储在数据库中,可以参照shiro权限的内容。

7、配置类

一:自定义权限异常信息处理类

//自定义处理异常信息
@Component  //注入到sring中,方便下面使用
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        //设置响应状态码,这里以403为例
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        //设置响应的请求头
        httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
        //这里直接打印
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}"); //这里双引号前需要加上\进行转译
        writer.flush();
        writer.close();
    }
}

注意,要在需要自定注入(@Autowired)到其他类中的配置类上添加@Component,例如该类,因为之后的springsecurity配置类需要使用到该类,所以这里要添加上@Component注解。

二:成功和失败的跳转链接
注意:这里可以重写成功和失败的跳转链接,但是如果不重写的话,只能跳转到本项目的链接,无法访问外部的项目。

1:自定义成功的跳转链接

//成功的跳转连接
//默认使用的是请求转发,不能访问外部的地址,无法解决前后端分离的项目,这里重写该方法,使用重定向的方式处理url,就可以跳转到外部了
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private String url;
    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //这里也可以打印用户的信息,注意这里的user指的是 springsecurity中的user
        User user = (User)authentication.getPrincipal();
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());  //注意,为了安全,这里密码会被打印成null,并不会真正打印出来
        System.out.println(user.getAuthorities()); //打印权限
        httpServletResponse.sendRedirect(url);
    }
}

2:自定义失败的跳转链接

//失败的跳转连接
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private String url;
    public MyAuthenticationFailureHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.sendRedirect(url);
    }
}

三:SpringSecurity核心配置类

注意:该配置类需要添加@Configuration注解

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private DataSource dataSource; 
    @Autowired
    private MyUserDetailsService userDetailsService;
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;
    @Autowired
    private PersistentTokenRepository persistentTokenRepository;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //启动登录验证,所有用户的所有请求都要经过登录校验,这里如果不指定登录页面则会访问默认的登录页面
        //http.formLogin().and().authorizeRequests().anyRequest().authenticated();

        http.formLogin() //表单提交
                .usernameParameter("user_name")  //定义要接收前台登录页面的用户名属性,这里要和前台input的name属性一样
                .passwordParameter("pass_word")   //定义要接收的密码,要和前台中输入密码的输入框的name属性一致
                .loginPage("/admin/login") //自定义登录页面
                .loginProcessingUrl("/admin/login")  //当发现是/admin/login时认为是登录,需要和表单提交的地址一样
                .successForwardUrl("/admin/index")  //登录成功的默认访问页面,仅限登录成功
                //.defaultSuccessUrl("/admin/index")  //登录成功的默认访问页面,和 .successForwardUrl()效果一样
                //.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))  //这里使用handler来代替上面两种方式,不能和上面两种方式同时出现,使用这个就可以跳转到外部链接了,可以用于前后端分离项目
                .failureForwardUrl("/admin/error")   //登录失败的页面,仅仅是登录失败,如果访问失败则会跳转到error.html,如果自己没有自定义error.html则会使用默认的,否则就会覆盖默认的,当然,如果自定义了异常处理,则会走自定义异常处理,例如我们定义的MyAccessDeniedHandler
                //.failureHandler(new MyAuthenticationFailureHandler("http://www.sywblog.xyz"))  //和上面的类似,注意重写的handler和failureForwardUrl不能同时出现 ,注意这里仅限于登录失败才会跳转到这个地方
                .and()
                .authorizeRequests()//授权认证,可以和上面分开,单独再开始一个http.authorizeRequests(),这里是写一块了
                .antMatchers("/admin/login").permitAll()//访问登录页面不需要被认证, ?表示匹配一个字符,*表示匹配0个或多个字符,**表示匹配0个或多个目录,注意:这里也可以加上允许被访问的请求方式,例如.antMatchers(HttpMethod.POST,"/admin/login").permitAll(),表示只允许post方式
                .antMatchers("/admin/error").permitAll()//失败页面不需要被认证,如果不放开这个,则登录失败之后还会跳转到登录页面,因为上面开放了登录页面,只有登录页面不需要认证
                .antMatchers("/js/**","/css/**","/img/**").permitAll()  //静态资源放行,也可以根据后缀名放行,例如:  /**/*.js  ,/**表示所有目录,*.js表示js文件,这放行所有目录下的js文件,上述放行之后可以直接访问http://localhost/img/canvas.png 找到我们的图片
                //.regexMatchers(".+[.]png").permitAll() //使用正则表达式,当然这里也同样可以加上请求的方式
                //.antMatchers("/admin/admin").hasAuthority("admin")  // 这里表示只有带有admin权限才可以访问该地址,这个方法只能指定一个权限
                //.antMatchers("/admin/normal").hasAnyAuthority("admin","root","normal")  //这里表示具有所列出权限中的任意一种权限就可以访问
                //.antMatchers("/admin/root").hasRole("syw")  //这里表示带有ROLE_syw角色的才能访问,要和MyUserDetailsService中的角色对应上,这里不用加ROLE_了,如果加上启动会直接报错
                //.antMatchers("/admin/root").hasAnyRole("syw,lmy") //这里表示带有ROLE_syw或者ROLE_lmy角色中的任何一个才能访问
                //.antMatchers("/admin/root").hasIpAddress("192.168.1.27")   //这里表示对应的ip才能访问
                .anyRequest().authenticated(); //所有请求都必须授权认证之后访问

        http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);//异常处理

        http.rememberMe()//记住我,默认失效时间为两周
                //rememberMeParameter() //定义前台接收的name属性的值,默认为remember-me
                .userDetailsService(userDetailsService)  //自定义登录业务逻辑
                .tokenRepository(persistentTokenRepository) //持久层对象,存储记住我的用户信息
                .tokenValiditySeconds(30);  //设置失效时间为30秒,注意需要关闭浏览器重新打开才能验证,因为浏览器不关闭就表示一次会话还没有结束

        http.logout()
                //.logoutUrl("/admin/logout")  //默认为/logout,自定义退出登录的路径,这个地方修改完之后要与退出的超链接对应,我们一般不去修改这个。
                .logoutSuccessUrl("/admin/login"); //自定义退出登录的跳转路径
        http.csrf().disable(); //关闭csrf防护
        http.headers().frameOptions().sameOrigin();
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        //为了安全起见,对密码进行加密
        return new BCryptPasswordEncoder();
    }
    //实例化持久层对象,用来实现记住我功能中,存储用户的登录信息
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);  //连接数据库
        //jdbcTokenRepository.setCreateTableOnStartup(true);  //自动建表名为 persistent_logins用来存储登录信息,第一次启动时候需要,第二次启动需要注释掉,不然会报错,如果第一次不加入这个,则退出登录功能会失败
        return jdbcTokenRepository;
    }
}

到此就结束了。

8、Shiro和SpringSecurity

Shiro

Apache Shiro是一个强大且易用的Java安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

执行流程


特点

  1. 1.易于理解的 Java Security API;

  2. 2.简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);

  3. 3.对角色的简单的签权(访问控制),支持细粒度的签权;

  4. 4.支持一级缓存,以提升应用程序的性能;

  5. 5.内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;

  6. 6.异构客户端会话访问;

  7. 7.非常简单的加密 API;

  8. 8.不跟任何的框架或者容器捆绑,可以独立运行。

Spring Security

Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowed to do?,也称为Authorization)。Spring Security在架构上将认证与授权分离,并提供了扩展点。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。

执行流程


  1. 1.客户端发起一个请求,进入 Security 过滤器链。

  2. 2.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。

  3. 3.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。

  4. 4.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。推荐:Java面试练题宝典

特点

shiro能实现的,Spring Security 基本都能实现,依赖于Spring体系,但是好处是Spring全家桶的亲儿子,集成上更加契合,在使用上,比shiro略负责。

两者对比

1.Shiro比Spring Security更容易使用,也就是实现上简单一些,同时基本的授权认证Shiro也基本够用

2.Spring Security社区支持度更高,Spring社区的亲儿子,支持力度和更新维护上有优势,同时和Spring这一套的结合较好。

3.Shiro 功能强大、且 简单、灵活。是Apache 下的项目比较可靠,且不跟任何的框架或者容器绑定,可以独立运行。

我的看法

如果开发的项目是Spring这一套,用Spring Security我觉得更合适一些,他们本身就是一套东西,顺畅,可能略微复杂一些,但是学会了就是自己的。如果开发项目比较紧张,Shiro可能更合适,容易上手,也足够用,Spring Security中有的,Shiro也基本都有,没有的部分网上也有大批的解决方案。

如果项目没有使用Spring这一套,不用考虑,直接Shiro。

全部评论