写在后面

在后面的学习当中,咱们对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/...