springboot安全
一、SpringSecurity
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
1、认识SpringSecurity
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
2、SpringSecurity实战
实战是最好的学习方式,简单写个案例。
1、引入 SpringSecurity 依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
2、新建config文件夹,创建SecurityConfig配置文件,并且继承 WebSecurityConfigurerAdapter,添加@EnableWebSecurity注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/level1/**").hasAnyRole("vip1") .antMatchers("/level2/**").hasAnyRole("vip2") .antMatchers("/level3/**").hasAnyRole("vip3"); http.formLogin().loginPage("/toLogin");
http.csrf().disable(); http.logout().logoutSuccessUrl("/");
http.rememberMe().rememberMeParameter("remember"); } }
|
注意这里用到了链式,当我们测试时发现没有权限的时候,会跳转到登录的页面
3、查看刚才登录页的注释信息;我们可以定义认证规则,继续编写SecurityConfig,重写configure(AuthenticationManagerBuilder auth)方法,也可以去jdbc中去取,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("lotus").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1"); }
|
这样一来,每个角色只能访问自己认证下的规则。并且开启了注销、记住我的功能
这个cookie的保存日期默认为14天,注销的时候,spring security 会帮我们自动删除这个 cookie
二、Shiro
Apache Shiro是一个强大且易用的Java安全框架,可以完成身份验证、授权、密码和会话管理
官网: http://shiro.apache.org/
1、认识Shiro
三个核心组件:Subject,SecurityManager 和 Realms

1、Subject
即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
2、SecurityManager
它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
3、Realm
Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
也就是说对于我们而言,最简单的一个Shiro应用:
应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入
2、基本功能点

- Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
- Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
- Web Support:Web 支持,可以非常容易的集成到 Web 环境;
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
- Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
3、Shiro实战
1、引入依赖
1 2 3 4 5
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.6.0</version> </dependency>
|
下面是编写配置文件
Shiro 三大要素
subject -> ShiroFilterFactoryBean —-当前用户
securityManager -> DefaultWebSecurityManager —-管理所有用户
Realm
实际操作中对象创建的顺序 : realm -> securityManager -> subject —-连接数据
2、定义配置类ShiroConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| @Configuration public class ShiroConfig {
@Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager);
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","perms[user:add]"); filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/*","authc"); bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/toLogin"); bean.setUnauthorizedUrl("/noauth");
return bean; }
@Bean(name = "securityManager" ) public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; }
@Bean(name = "userRealm") public UserRealm userRealm(){ return new UserRealm(); }
@Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }
|
3、自定义MyRealm类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class UserRealm extends AuthorizingRealm {
@Autowired UserService userService;
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了授权方法"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject(); User currentUser = (User)subject.getPrincipal(); info.addStringPermission(currentUser.getPerms()); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("执行了认证方法");
UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
User user = userService.queryUserByName(usertoken.getUsername());
if (user == null){ return null; }
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
} }
|
4、定义UserController类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @Controller public class MyController {
@RequestMapping({"/","/index"}) public String toIndex(Model model){ model.addAttribute("msg","hello,Shiro"); return "index"; }
@RequestMapping("/user/add") public String add(){ return "user/add"; }
@RequestMapping("/user/update") public String update(){ return "user/update"; }
@RequestMapping("/toLogin") public String toLogin(){ return "login"; }
@RequestMapping("/login") public String login(String username,String password,Model model){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try { subject.login(token); return "index"; }catch (UnknownAccountException e){ model.addAttribute("msg","用户名错误"); return "login"; }catch (IncorrectCredentialsException e){ model.addAttribute("msg","密码错误"); return "login"; } } @RequestMapping("/noauth") @ResponseBody public String unauthorized(){ return "未授权无法访问此页面"; } }
|
5、整合Mybatis
需要导入的依赖:①mysql-connect ②druid ③log4j ④mybtis-spring-boot-starter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
|
编写配置文件application.properties或者application.yaml
编写lotus文件下的pojo,controller,service,mapper文件夹参考前面的springboot整合mybatis代码
在mapper中添加了一个queryUserByName的方法
1 2 3 4 5 6 7 8 9 10
| @Service public class UserServiceImpl implements UserService {
@Autowired UserMapper userMapper; @Override public User queryUserByName(String name) { return userMapper.queryUserByName(name); } }
|
这样一来,我们就简单的体验了shiro的授权和认证功能,值得一提的是,前端页面可以和thymeleaf整合,写出如一下代码的判断
1 2 3 4
| <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">add</a> </div>
|
index页面
由于目前登录的用户没有add权限,拥有update字段,所以显示update模块
三、Shiro 和 Spring Security区别
先说个人结论:Shiro 首选 ,上手快 ,也足够用,自由度高,Spring Security中有的,Shiro也基本都有(项目没有使用Spring这一套,不用考虑,直接Shiro)
如果开发项目使用Spring这一套,用Spring Security可能更合适一些;虽然Spring Security 比较复杂,但与Spring 家族结合能力更强,是一个可以放心选择的框架结构
- Shiro比Spring更容易使用,实现和最重要的理解
- Spring Security更加知名的唯一原因是因为品牌名称
- “Spring”以简单而闻名,但讽刺的是很多人发现安装Spring Security很难
- 然而,Spring Security却有更好的社区支持
- Apache Shiro在Spring Security处理密码学方面有一个额外的模块
- Spring-security 对spring 结合较好,如果项目用的springmvc ,使用起来很方便。但是如果项目中没有用到spring,那就不要考虑它了。
- Shiro 功能强大、且 简单、灵活。是Apache 下的项目比较可靠,且不跟任何的框架或者容器绑定,可以独立运行
个人认为现阶段需求,权限的操作粒度能控制在路径及按钮上,数据粒度通过sql实现。Shrio简单够用。
至于OAuth,OpenID 站点间统一登录功能,现租户与各个产品间单点登录已经通过cookies实现,所以Spring Security的这两个功能可以不考虑。
SpringSide网站的权限也是用Shrio做的。
shiro有很多地方都比spring security方便简单直接,比起spring security的庞大模式更容易理解和切入一些,而spring security比shiro功能上要多一点,再就是和spring框架的无缝对接,比如支持spel等,有时候比shiro更方便灵活。不过spring security的很多源代码我看了感觉可插拔性设计的不够优化,想自己扩展的话要做很多无谓的工作。