一、Spring boot与安全
1、安全
应用程序的两个主要区域是“认证”和“授权”(或者访问控制),这两个主要区域是安全的两个目标。 身份验证意味着确认您自己的身份,而授权意味着授予对系统的访问权限
-
认证
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。系统确定您是否就是您所说的使用凭据。在公共和专用网络中,系统通过登录密码验证用户身份。身份验证通常通过用户名和密码完成,
-
授权
另一方面,授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。简单来说,授权决定了您访问系统的能力以及达到的程度。验证成功后,系统验证您的身份后,即可授权您访问系统资源。
2、Spring Security
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。
- WebSecurityConfigurerAdapter:自定义Security策略
通过在配置类中继承该类重写configure(HttpSecurity http)方法来实现自定义策略
- @EnableWebSecurity:开启WebSecurity模式
在配置类上标注@EnableWebSecurity开启WebSecurity模式
3、 Springboot整合security
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 登录和注册
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// .denyAll(); //拒绝访问
// .authenticated(); //需认证通过
// .permitAll(); //无条件允许访问
//.hasRole("vip1"); //指定用户访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/*").hasRole("vip1")
.antMatchers("/level2/*").hasRole("vip2")
.antMatchers("/level3/*").hasRole("vip3");
//登陆功能
http.formLogin().loginPage("/login").usernameParameter("username").passwordParameter("password");
//关闭防止网址攻击,这样注销功能才能正常使用
http.csrf().disable();
//注销功能,注册完成后跳转到index页面
http.logout().logoutSuccessUrl("/index");
此时除了主页,点击其他的页面都会自动跳转到security自动生成的登录页面,/login
来到登陆页,重定向到/login?error
表示登陆失败;除此之外也可以使用loginPage("")
跳转到自己的登录页面。
http.logout()
开启自动配置的注销功能,向/logout
发送post请求表示注销,需要在欢迎页加上注销表单,默认注销后自动跳转到登录页面,若想改变转发路径,可以通过logoutSuccessUrl(url)
设置路径
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销">
</form>
- 定义认证规则
为了保证密码能安全存储,springboot内置PasswordEncoder
对密码进行转码,默认密码编码器为DelegatingPasswordEncoder
。在定义认证规则时,我们需要使用PasswordEncoder
将密码转码,我们可以使用BCryptPasswordEncoder()
来对用户密码进行加密。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//.passwordEncoder(new BCryptPasswordEncoder() 密码加密方式
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).
withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and().withUser("yue").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
.and().withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
- 自定义页面
引入命名空间
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
首页显示
<!--首页显示-->
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<!--登录注销-->
<div class="right menu">
<!--未登录-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item" >
用户名:<span sec:authentication="name"> </span>
</a>
</div>
<!--已登录-->
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="address card icon"></i> 注销
</a>
</div>
</div>
</div>
</div>
根据不同用户显示不同页面
<!--根据不同用户显示不同页面-->
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
- 记住我功能
//记住我
http.rememberMe().rememberMeParameter("remember");
<div class="field">
<input type="checkbox" name="remember">记住我
</div>
通过loginPage(url)
设置登录页路径后,在定制的登录页发送post url
即为登录请求,并设置表单的name
属性都为对应值;
通过勾选记住我
,session退出后依然能通过cookie
保存用户信息,下次免登陆。
4、Apache Shiro
Apache Shiro 是 Java 的⼀个安全(权限)框架。Shiro 可以轻松的完成:身份认证、授权、加密、会话管理等功能Shiro 可以⾮常容易的开发出⾜够好的应⽤,其不仅可以⽤在JavaSE 环境,也可以⽤在 JavaEE 环境。功能强⼤且易⽤,可以快速轻松地保护任何应⽤程序 ( 从最⼩的移动应⽤程序到最⼤的Web和企业应⽤程序)⽅便的与Web 集成和搭建缓存。
1. 功能简介:
- Authentication:身份认证/登录,验证⽤户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的⽤户是否拥有某个权限;即判断⽤户是否能进⾏什么操作。如:验证某个⽤户是否拥有某个⻆⾊。或者细粒度的验证某个⽤户对某个资源是否具有某个权限;
- Session Manager:会话管理,即⽤户登录后就是⼀次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,⽽不是明⽂存储;
- Web Support:Web ⽀持,可以⾮常容易的集成到Web 环境;
- Caching:缓存,⽐如⽤户登录后,其⽤户信息、拥有的⻆⾊/权限不必每次去查,这样可以提⾼效率;
- Remember Me:记住我,这个是⾮常常⻅的功能,即⼀次登录后,下次再来的话可以⽴即知道你是哪个⽤户
2. 工作流程:
shiro 运⾏流程中,3个核⼼的组件:Subject、SecutiryManager、Realm
-
Subject
安全校验中,最常⻅的问题是"当前⽤户是谁?" “当前⽤户是否有权做x事?”,所以考虑安全校验过程最⾃
然的⽅式就是基于当前⽤户。Subject 代表了当前“⽤户”,
应⽤代码直接交互的对象是 Subject,只要得到Subject对象⻢上可以做绝⼤多数的shiro操作。
也就是说 Shiro 的对外API 核⼼就是 Subject。
Subject 会将所有交互都会委托给 SecurityManager。Subject是安全管理中直接操作的对象
-
SecurityManager
安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;
且其管理着所有 Subject;它是 Shiro的核⼼,
它负责与 Shiro 的其他组件进⾏交互,它相当于 SpringMVC 中DispatcherServlet 的⻆⾊ -
Realm
Shiro 从 Realm 获取安全数据(如⽤户、⻆⾊、权限),就是说SecurityManager 要验证⽤户身份,那么
它需要从 Realm 获取相应的⽤户进⾏⽐较以确定⽤户身份是否合法;
也需要从 Realm 得到⽤户相应的⻆⾊/权限进⾏验证⽤户是否能进⾏操作;
可以把 Realm 看成 DAO,( 数据访问⼊⼝ )
3. shiro标签:
- 身份认证
标签 | 作用 |
---|---|
<shiro:authenticated> | 已登录 |
<shiro:user> | 已登录或记住我 |
<shiro:guest> | 游客(未登录 且 未记住我) |
<shiro:notauthenticated> | 未登录 |
<shiro:principal> | 获取⽤户身份信息 |
- 角色校验
<shiro:hasAnyRoles name="admin,manager">是其中任何⼀种⻆⾊
<shiro:hasRole name="admin">是指定⻆⾊
<shiro:lacksRole name="admin">不是指定⻆⾊
- 权限校验
<shiro:hasPermission name=“user:delete”>
有指定权限
<shiro:lacksPermission name=“user:delete”>
缺失指定权限
<td>
<a href="#" style="text-decoration:none">查看详情</a>
<shiro:hasPermission name="user:delete">
<a href="#" style="text-decoration: none">删除</a>
</shiro:hasPermission>
<shiro:lacksPermission name="user:delete">
<a href="#" style="text-decoration: none" >⽆权删除</a>
</shiro:lacksPermission>
</td>
5、SpringBoot整合Shiro
- 导入依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</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.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
- 登录和注册
登录功能实现
//设置登陆页面
bean.setLoginUrl("/login");
<div shiro:notAuthenticated>
<a th:href="@{/toLogin}">登陆</a>
</div>
@PostMapping("/login")
public String login(Model model,String username,String password){
//获取当前用户
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";
}
}
注册功能实现
<!--已登陆-->
<div shiro:Authenticated>
<a th:href="@{/toLogout}">注销</a>
</div>
@GetMapping("/toLogout")
public String toLogout() {
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//移除当前用户的信息
subject.logout();
return "index";
}
- 定义认证规则
@Controller
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
/*
* 添加shiro内置过滤器
* anon:无需认证即可访问
* authc:需要认证才能访问
* perms:需要某个资源权限才能访问
* role:需要某个角色才能访问
* user:必须拥有“记住我”功能才能使用
* */
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("/login");
//设置未授权的页面
bean.setUnauthorizedUrl("/unauthorized");
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 getUserRealm(){
return new UserRealm();
}
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
}
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//授权
System.out.println("执行授权方法~");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//info.addStringPermission("user:add"); //经过授权方法,赋值user:add权力
Subject subject = SecurityUtils.getSubject();
//从subject中获取当前用户对象
User tempUser = (User) subject.getPrincipal();
//获取当前用户权限并赋值
info.addStringPermission(tempUser.getPerms());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//认证
System.out.println("执行认证方法~");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.selectByName(token.getUsername());
if (user == null){
return null; //抛出异常
}
//密码认证shiro自动认证,将user对象放入subject中
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
- 自定义页面
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<div shiro:notAuthenticated>
<a th:href="@{/toLogin}">登陆</a>
</div>
<!--已登陆-->
<div shiro:Authenticated>
<a th:href="@{/toLogout}">注销</a>
</div>
</hr>
<div shiro:hasPermission="user:add">
<a th:href="@{user/add}">add</a>
</div>
</hr>
<div shiro:hasPermission="user:update">
<a th:href="@{user/update}">update</a>
</div>