写在后面
在后面的学习当中,咱们对spring security有了一个小小的意识,接下来咱们整合目前的支流框架springBoot,实现权限的治理。
在这之前,假设你曾经理解了基于资源的权限治理模型。数据库设计的表有 user 、role、user_role、permission、role_permission。
步骤:
默认大家都曾经数据库曾经好,曾经有了下面提到的表。(文末提供sql脚本下载)
第一步:在pom.xml文件中引入相干jar包
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>pers.lbf</groupId> <artifactId>springboot-spring-securioty-demo1</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-spring-security-demo1</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</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>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> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
第二步:批改application.yml文件,增加数据库相干配置
server: port: 8081spring: datasource: url: jdbc:mysql://127.0.0.1:3306/secutiry_authority?useSSL=false&serverTimezone=GMT username: root password: root1997 driver-class-name: com.mysql.cj.jdbc.Driver
第三步:启动我的项目
springboot曾经给咱们提供好了一个默认的username为“user”,其明码能够在控制台输入中失去。并且在springBoot的默认配置中,所有资源必须要通过认证后能力拜访
关上<http://127.0.0.1:8081/login 即可看到默认的登录页面。
第四步:增加配置类,笼罩springBoot对spring security的默认配置
/** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/8/28 20:22 */@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userService; @Autowired private BCryptPasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(passwordEncoder); } @Override protected void configure(HttpSecurity http) throws Exception { //禁用跨域爱护 http.csrf().disable(); //配置自定义登录页 http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/login") .defaultSuccessUrl("/") .usernameParameter("username") .passwordParameter("password"); //配置登出 http.logout() .logoutUrl("/logout") .logoutSuccessUrl("/login.html"); } @Override public void configure(WebSecurity webSecurity) throws Exception{ //疏忽动态资源 webSecurity.ignoring().antMatchers("/assents/**","/login.html"); }}
对于EnableGlobalMethodSecurity注解的阐明
@EnableGlobalMethodSecurity(securedEnabled=true)
开启@Secured 注解过滤权限
@EnableGlobalMethodSecurity(jsr250Enabled=true)
开启@RolesAllowed 注解过滤权限
@EnableGlobalMethodSecurity(prePostEnabled=true)
应用表达式工夫办法级别的安全性 4个注解可用
@PreAuthorize 在办法调用之前,基于表达式的计算结果来限度对办法的拜访 @PostAuthorize 容许办法调用,然而如果表达式计算结果为false,将抛出一个安全性异样 @PostFilter 容许办法调用,但必须依照表达式来过滤办法的后果 @PreFilter 容许办法调用,但必须在进入办法之前过滤输出值
第五步:编写代码,实现对User、role、permission的CRUD
5.1 编写本人的user对象,实现spring security的UserDetails接口,并实现对User的查找操作
对于为什么要实现这个接口,大家能够参考我上一篇文章《Spring Security认证流程剖析--练气前期》。
/** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/8/28 22:14 */public class UserDO implements UserDetails { private Integer id; private String username; private String password; private Integer status; private List<SimpleGrantedAuthority> authorityList; @Override public String toString() { return "UserDO{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", status=" + status + ", authorityList=" + authorityList + '}'; } public List<SimpleGrantedAuthority> getAuthorityList() { return authorityList; } public void setAuthorityList(List<SimpleGrantedAuthority> authorityList) { this.authorityList = authorityList; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public void setStatus(Integer status) { this.status = status; } /** * Returns the authorities granted to the user. Cannot return <code>null</code>. * * @return the authorities, sorted by natural key (never <code>null</code>) */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorityList; } /** * Returns the password used to authenticate the user. * * @return the password */ @Override public String getPassword() { return this.password; } /** * Returns the username used to authenticate the user. Cannot return <code>null</code>. * * @return the username (never <code>null</code>) */ @Override public String getUsername() { return this.username; } /** * Indicates whether the user's account has expired. An expired account cannot be * authenticated. * * @return <code>true</code> if the user's account is valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ @Override public boolean isAccountNonExpired() { return this.status==1; } /** * Indicates whether the user is locked or unlocked. A locked user cannot be * authenticated. * * @return <code>true</code> if the user is not locked, <code>false</code> otherwise */ @Override public boolean isAccountNonLocked() { return this.status == 1; } /** * Indicates whether the user's credentials (password) has expired. Expired * credentials prevent authentication. * * @return <code>true</code> if the user's credentials are valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ @Override public boolean isCredentialsNonExpired() { return true; } /** * Indicates whether the user is enabled or disabled. A disabled user cannot be * authenticated. * * @return <code>true</code> if the user is enabled, <code>false</code> otherwise */ @Override public boolean isEnabled() { return this.status==1; }}
对于用户凭证是否过期、账户是否被锁定大家能够本人实现一下
** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/8/28 22:17 */public interface IUserDao { @Select("select * from sys_user u where u.username=#{name}") UserDO findByName(String name);}
5.2 编写Role和Permission两个实体类,并实现对其查找的Dao
RoleDO
/** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/9/1 20:51 */public class RoleDO implements Serializable { private Integer id; private String roleName; private String roleDesc;}
PermissionDO
/** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/9/1 21:27 */public class PermissionDO implements Serializable { private Integer id; private String permissionName; private String permissionUrl; private Integer parentId; }
IRoleDao
/** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/9/1 20:53 */public interface IRoleDao { @Select("select * from sys_role sr where sr.id in (select rid from sys_user_role where uid=#{userId})") List<RoleDO> findByUserId(Integer userId);}
IPermissionDao
/** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/9/1 21:30 */public interface IPermissonDao { @Select("select * from sys_permission sp where sp.id in (select pid from sys_role_permission where rid=#{roleId})") List<PermissionDO> findByRoleId(Integer roleId);}
5.3 编写UserService 实现UserDetailsService接口并实现loadUserByUsername办法
** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/8/28 22:16 */@Service("userService")public class UserServiceImpl implements UserDetailsService { @Autowired private IUserDao userDao; @Autowired private IRoleDao roleDao; @Autowired private IPermissonDao permissonDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username == null){ return null; } UserDO user = userDao.findByName(username);//加载权限 List<RoleDO> roleList = roleDao.findByUserId(user.getId()); List<SimpleGrantedAuthority> list = new ArrayList<> (); for (RoleDO roleDO : roleList) { List<PermissionDO> permissionListItems = permissonDao.findByRoleId(roleDO.getId()); for (PermissionDO permissionDO : permissionListItems) { list.add(new SimpleGrantedAuthority(permissionDO.getPermissionUrl())); } } user.setAuthorityList(list); return user; }}
第六步:编写一个测试接口
/** * @author 赖柄沣 bingfengdev@aliyun.com * @version 1.0 * @date 2020/8/27 20:02 */@RestController@RequestMapping("/product")public class TestController { @GetMapping("/") @PreAuthorize("hasAuthority('product:get')") public String get() { return "调用胜利"; }}
第七步 :应用postman进行测试
7.1登录操作
登录成绩返回主页
登录失败返回登录页面
7.2调用受爱护的接口
有权限则调用胜利
无权限返回403
大家能够实现一下对异样的拦挡,给用户返回一个敌对的提醒。
写在最初
这是springBoot整合spring security单体利用的一个小demo。对于分布式的、应用JWT代替spring security 的csrf,并自定义认证器的例子将在我的下一篇文章中介绍。
代码及sql脚本下载:https://github.com/code81192/...