赞
踩
理解什么是权限
学习Spring Security框架的基本概练和用法
能够使用Spring Security写一个入门级的安全应用
为了让我们的接口能够根据用户的权限进行一定限制,我们引入了Security,通过权限,我们能让其在登陆后一段时间,能自由访问权限内的接口,但是不能访问权限外的接口方法。
为此我们需要对之前的项目进行改造,具体内容在以下几个部分;
2.1 导入maven依赖
- <!-- springboot启动器-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <!-- lombok-->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <!-- test测试-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- Web-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <!-- mybatis和mysql驱动-->
- <!-- mybatis-->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>${mybatis.springboot.version}</version>
- </dependency>
- <!-- mysqsl-->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>${mysql.version}</version>
- </dependency>
-
- <!-- 分页-->
- <dependency>
- <groupId>com.github.pagehelper</groupId>
- <artifactId>pagehelper-spring-boot-starter</artifactId>
- <version>1.4.3</version>
- </dependency>
-
- <dependency>
- <groupId>com.github.pagehelper</groupId>
- <artifactId>pagehelper</artifactId>
- <version>5.3.1</version>
- </dependency>
-
- <!-- 导入JPA依赖 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- <!-- Swagger-->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>2.9.2</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>2.9.2</version>
- </dependency>
- <dependency>
- <groupId>net.minidev</groupId>
- <artifactId>json-smart</artifactId>
- </dependency>
- <!-- redis-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!-- 日志收集-->
- <dependency>
- <groupId>org.aspectj</groupId>
- <artifactId>aspectjweaver</artifactId>
- <version>1.9.7</version>
- <scope>compile</scope>
- </dependency>
- <!-- Security-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
2.2 创建一个index门户接口
当前接口用于测试我们的权限认证和授权
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.security.access.prepost.PreAuthorize;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.context.SecurityContext;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- @RestController
- @Slf4j
- public class IndexController {
-
- //test 1 : anyone could access
- @RequestMapping("/index")
- public String getAllUsers() {
- log.info("访问 index..");
- return "访问 index....";
- }
-
- //test 2 : someone has the Authority:'cx:updates_user' could access
- @RequestMapping("/users")
- @PreAuthorize("hasAuthority('cx:updates_user')")// 授权:有cx:updates_user权限才能做该操作 否则报错403
- public String update() {
- //获取上下文
- SecurityContext securityContext = SecurityContextHolder.getContext();
- Authentication authentication = securityContext.getAuthentication();
- //在页面返回当前登录用户的所有权限
- return authentication.toString();
- }
-
- }
2.3 创建Security配置类WebSecurityConfigure
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
-
- // 这个有了以下任何一个注解都可以不用这个注解
- // @Configuration
- // 这个表示启用Web安全的注解,如果你已经是是一个web 项目,不需要使用此注解,
- // @EnableWebSecurity //Springboot的自动配置机制WebSecurityEnablerConfiguration已经引入了该注解
-
- //开启这个来判断用户对某个控制层的方法是否具有访问权限(见ProductController的@PreAuthorize)
- // 这个注解很重要,如果没有这个注解,那么Controller里的方法将不受约束,只要登录成功就能访问。
- @EnableGlobalMethodSecurity(prePostEnabled = true) //至关重要的注解,缺失导致验证不起效
- @EnableWebSecurity
- public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
-
- @Autowired
- private UserDetailsService userDetailsService;
-
- // 参数: HttpSecurity http
- //**http.authorizeRequests()**
- // 下添加了多个匹配器,每个匹配器用来控制不同的URL接受不同的用户访问。
- // 简单讲,http.authorizeRequests()就是在进行请求的权限配置。
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- //第二步:我们用我们自己的数据库数据来完成权限验证
- http.authorizeRequests()
- .antMatchers("/index").permitAll()//放行
- .anyRequest().authenticated()
- .and()
- .formLogin()
- }
-
- //这里配置密码为 BCrypt 加密方式,这样创建用户时,会对密码进行加密。而不是明文存储。
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
- }
2.4 配置yml文件
spring中加入security(注意层级)
spring: security: user: name: jing password: 1234
完成到这里,我们已经能够对无权访问的接口进行限制了。
尝试访问接口
接下来进一步完善我们的security
2.5 创建UserDetails
使用之前的User实体类 实现UserDetails接口并实现其方法
- package com.wanxi.springboot1018.entity;
-
- import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
- import io.swagger.annotations.ApiModel;
- import io.swagger.annotations.ApiModelProperty;
- import lombok.Data;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.stereotype.Component;
- import java.io.Serializable;
- import java.util.Collection;
- import java.util.HashSet;
- import java.util.Set;
-
- @Data
- @Component
- @ApiModel(value = "用户",description = "用于描述用户对象")
- @JsonIgnoreProperties({"enabled","accountNonExpired", "accountNonLocked", "credentialsNonExpired", "authorities"}) //避免把userdetail接口的方法序列化
- public class User extends Base implements UserDetails {
-
- @ApiModelProperty(value = "用户ID",example = "123")
- private int id=0;
- @ApiModelProperty(value = "用户密码",example = "abc")
- private String password="";
- @ApiModelProperty(value = "用户姓名",example = "jing")
- private String username="";
- @ApiModelProperty(value = "用户电话",example = "180****8963")
- private String tel="";
- @ApiModelProperty(value = "生产日期",example = "2000-08-13")
- private String birthday="";
- @ApiModelProperty(value = "性别",example = "男")
- private String sex="";
- @ApiModelProperty(value = "授权变量",example = "")
- private Set<? extends GrantedAuthority> authorities= new HashSet<>();
-
- private Boolean A ;
- private Boolean B ;
-
- public Boolean getA() {
- return A;
- }
-
- public void setA(Boolean a) {
- A = a;
- }
-
- public Boolean getB() {
- return B;
- }
-
- public void setB(Boolean b) {
- B = b;
- }
-
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- return authorities;
- }
-
- //账号是否过期 count has expired
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- //账号是否上锁 count has locked
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- //令牌是否过期 报错:credentials have expired
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- //是否启用 报错:User is disabled 由于没有status状态这个字段,默认启用。
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String toString() {
- return "User{" +
- "id=" + id +
- ", password='" + password + '\'' +
- ", name='" + username + '\'' +
- '}';
- }
-
- @Override
- public String getPassword() {
- return this.password;
- }
-
- @Override
- public String getUsername() {
- return this.username;
- }
-
- }
-
2.6 创建GrantedAuthority
新建一个类:Permission 表示用户的一个权限
并实现GrantedAuthority接口
(注意,这个实体类的成员一定要和数据库的字段相对应)
- import lombok.Data;
- import org.springframework.security.core.GrantedAuthority;
-
- import java.util.Date;
-
- /**
- *@description: 授予的权限信息,要实现GrantedAuthority
- */
- @Data
- // 不使用@Builder时以下@AllArgsConstructor和@NoArgsConstructor都可以不要,使用了就要需要,不然mybatis构建对象时会出错。
- //@Builder
- //@AllArgsConstructor
- //@NoArgsConstructor
- public class Permission implements GrantedAuthority {
-
- private Integer id;
- private Integer pid;
- private String name;
- private String value;
- private String icon;
- private Integer type;
- private String uri;
- private Integer status;
- private Date createTime;
- private String sort;
-
- //获取权限
- @Override
- public String getAuthority() {
- // 这里返回的内容要和Controller里的@PreAuthorize("hasAuthority('wx:product:read')")匹配
- return this.value;
- }
- }
-
2.7 创建UserDetailsService
不同与之前的UserService,这个接口实现类会调用userService的方法,并被Security调用
- import com.wanxi.springboot1018.entity.Permission;
- import com.wanxi.springboot1018.entity.User;
- import com.wanxi.springboot1018.service.UserService;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Service;
- import javax.annotation.Resource;
- import java.util.HashSet;
- import java.util.List;
-
- /**
- *@description: UserDetailsService的实现类,Security 安全框架会调用这个接口的loadUserByUsername。
- * 这个类是Security 框架定义的接口,不是我们自己业务定义的接口,
- * 要想Security 按照我们的逻辑起作用,我们需要实现它
- */
- @Service("userDetailsService")
- public class UserDetailServiceImpl implements UserDetailsService {
- @Resource
- UserService userService;
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- //通过用户名 访问数据库拿到 当前用户对象
- User user= userService.getUserByName(username);
- //一定要设置为加密后的密码
- //user.setName(username);
- user.setName("jing");
- user.setPassword("$2a$10$1M8F40YGBvgZrp0/UYtGxOTTFjiWxdXik1x1b.qliRk2tnOyWBv2i");
- // 紧接着 调用getPermissionsByUserId 方法 获取当前用户的 权限(用户->角色->权限)
- List<Permission> permissionList= userService.getPermissionsByUserId(user.getId());
- // 创建 HashSet 取代 List
- HashSet<Permission> permissions = new HashSet<>(permissionList);
- // 存入user对象
- user.setAuthorities(permissions);
- // 返回对象,包含该用户的所有权限
- return user;
- }
- }
2.8 修改Config配置类
- http.authorizeRequests()
- .antMatchers("/index").permitAll()//放行
- .anyRequest().authenticated()
- .and()
- .formLogin()
- .and()
- // 这一步,告诉Security 框架,我们要用自己的UserDetailsService实现类
- // 来传递UserDetails对象给框架,框架会把这些信息生成Authorization对象使用
- .userDetailsService(userDetailsService);
在这里加上后面的userDetailsService。
@PreAuthorize("hasAuthority('cx:updates_user')")// 授权:有cx:updates_user权限才能做该操作 否则报错403
这里的注解已经加上了
所以直接访问测试即可
2.10 访问测试
登录
报错403:权限不够
证明了我们的权限能够正常生效。并且拦截正确
2.11 其他验证:
一、把@PreAuthorize("hasAuthority('wx:product:read')") 这个值改一改,改成没有的试试
如果有注解但是没有权限:
如果没有注解:能够拿到数据
二、 把Config 类的@EnableGlobalMethodSecurity注解去掉,看看权限是否生效
恢复上面的注解
我们吧@EnableGlobalMethodSecurity去掉后,
尝试直接跨过登录访问无权限限制的接口:
尝试直接跨过登录访问无权限限制的接口:回到了登录界面
登录后再进行无权访问的接口:
可以看到,只要能登陆进来,就能访问,权限就是摆设
3.1
这些图需要在看完代码构成后,才能真正去的立体化去理解流程。
4.1 权限六表概念图
4.2 Secutity 流程图
4.3 Secutity 构成
以上是10月31日对29日的Security学习进行日常总结。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。