SpringBoot2.1版本的我的应用开发框架 - 使用SpringSecurity管理咱们的访问权限2

本篇做为SpringBoot2.1版本的我的开发框架 子章节,请先阅读SpringBoot2.1版本的我的开发框架再次阅读本篇文章html

后端项目地址:SpringBoot2.1版本的我的应用开发框架前端

前端项目地址:ywh-vue-adminvue

参考:java

在上一篇文章咱们对spring security有了初步认识之后,咱们这篇主要实现 从数据库查询用户来进行用户是否具备登录的认证。git

数据库表的设计

参考:github

  • RBAC权限管理 这篇文章讲的很是详细,只不过有点久远,12年我还在上高一。。。

咱们在作用户登录以前,咱们先要设计数据库表,RBAC(Role-Based Access Control,基于角色的访问控制),就是用户经过角色与权限进行关联。简单地说,一个用户拥有若干角色,每个角色拥有若干权限。这样,就构形成“用户-角色-权限”的受权模型。在这种模型中,用户与角色之间,角色与权限之间,通常者是多对多的关系。spring

在这里插入图片描述
按照我参考的博客中的设计好了五张基础表,字段的话能够根据本身的需求变化,生成的sql文件我放到我项目中了。

表结构肯定好之后,咱们在项目中把系统用户的entity、dao、service、以及dao对应的xml都设置好,这里我就不一一展现代码了,能够拿以前的MybatisPlus的自动生成,正好本身又能够复习一遍以前的,添加好之后能够如今测试类中确认一下,没有错误再进行下一步。sql

security核心类

在上一篇笔记中咱们知道security的核心类之一的WebSecurityConfigurerAdapter,咱们能够在这个配置类中建立本身的用户,放行哪一个请求,认证什么请求,在这篇笔记咱们要实现从数据库中查询用户,因此咱们要对如下核心类作实现。数据库

  • UserDetails接口
  • UserDetailsService接口

UserDetails接口

UserDetails接口是security为咱们提供的可扩展的用户信息接口,保存一些非敏感类的信息,在security模块下entity包中实现此接口类,其实就是一个实体类。后端

package com.ywh.security.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;

/** * CreateTime: 2019-01-24 16:10 * ClassName: SecurityUserDetails * Package: com.ywh.security.entity * Describe: * security的用户详情类 * * @author YWH */
public class SecurityUserDetails implements UserDetails {


    /** * 用户的密码 */
    private String password;

    /** * 用户的名字 */
    private String username;

    /** * 用户状态,1 表示有效用户, 0表示无效用户 */
    private Integer state;

    /** * 用户的权限,能够把用户的角色信息的集合先放进来,角色表明着权限 */
    private Collection<? extends GrantedAuthority> authorties;


    public SecurityUserDetails(String password, String username, Integer state, Collection<? extends GrantedAuthority> authorties) {
        this.password = password;
        this.username = username;
        this.state = state;
        this.authorties = authorties;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorties;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    /** * 指示用户的账户是否已过时。过时的账户没法经过身份验证。 * @return true若是用户的账户有效(即未过时), false若是再也不有效(即已过时) */
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /** * 指示用户是锁定仍是解锁。锁定的用户没法进行身份验证。 * @return true是未锁定,false是已锁定 */
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /** * 指示用户的凭据(密码)是否已过时。过时的凭据会阻止身份验证 * @return true若是用户的凭证有效(即未过时), false若是再也不有效(即已过时) */
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /** * 指示用户是启用仍是禁用。禁用的用户没法进行身份验证。 * @return true用户已启用,false用户已经禁用 */
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return state == 1;
    }
}

复制代码

UserDetailsService接口

此接口主要重写一个方法就行了loadUserByUsername,这个方法主要做用就是经过用户名查询用户的详细信息,而后放到咱们的UserDetails 实现的子类中,保存在security的SecurityContextHolder中,上一篇咱们经过这个来获取用户信息的。

在security模块中的service/impl中实现此接口,selectByUserName方法就是一个普通的dao接口,在mapper.xml文件中定义好sql语句,因为很简单我就不贴了

package com.ywh.security.service.impl;

/** * CreateTime: 2019-01-25 16:39 * ClassName: SecurityUserDetailsServiceImpl * Package: com.ywh.security.service.impl * Describe: * UserDetailService的实现类 * 这个@Primary表示这个类所继承的接口有多个实现类,当不知道引入哪一个的时候,优先使用@Primary所注解的类 * @author YWH */
@Primary
@Service
public class SecurityUserDetailsServiceImpl implements UserDetailsService {

	// 用户查询接口
    private SysUserDao sysUserDao;

    @Autowired
    public SecurityUserDetailsServiceImpl(SysUserDao sysUserDao) {
        this.sysUserDao = sysUserDao;
    }

	@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUserEntity sysUserEntity = sysUserDao.selectByUserName(username);
        if(sysUserEntity != null){
            // stream java8的新特性,Stream 使用一种相似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
            // 参考http://www.runoob.com/java/java8-streams.html
            List<SimpleGrantedAuthority> collect = sysUserEntity.getRoles().stream().map(SysRoleEntity::getSysRoleName)
                    .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            return new SecurityUserDetails(sysUserEntity.getSysUserPassword(),sysUserEntity.getSysUserName(),sysUserEntity.getSysUserState(),collect);
        }
        throw MyExceptionUtil.mxe(String.format("'%s'.这个用户不存在", username));
    }
}

复制代码

修改WebSecurityConfigurerAdapter实现类

在上一篇中咱们对此类进行了重写,咱们知道用户配置是重写configure(AuthenticationManagerBuilder auth)方法,咱们是经过内存管理器来建立用户的,这回咱们实现了UserDetailsService接口,咱们就能够经过这个来查询用户并使用。 具体修改以下:

package com.ywh.security.config;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {

	private UserDetailsService userDetailsService;


    @Autowired
    public SecurityConfigurer(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
    
    /** * 用户信息配置 * @param auth 用户信息管理器 * @throws Exception 异常信息 */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//修改之前
// auth
// .inMemoryAuthentication()
// .withUser("root")
// .password("root")
// .roles("user")
// .and()
// .passwordEncoder(CharEncoder.getINSTANCE());

//修改之后
        auth
                .userDetailsService(this.userDetailsService)
                .passwordEncoder(passwordEncoder());

    }
    
	/** * 密码加密 * @return 返回加密后的密码 */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
复制代码

重启项目后咱们能够看到效果以下,ywh这个用户是数据库中的,这个用户的密码是我手动调用PasswordEncoder 加密事后放到数据库中的,密码都是123。

@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void getOneTest(){
    System.out.println("password:" + passwordEncoder.encode("123"));
}
复制代码

在这里插入图片描述
若是输入一个数据库中没有的用户,则会提示你查无此人
在这里插入图片描述

自定义登录

参考:Spring Security自定义用户登录认证

到此步以后咱们完成了从数据库中查询用户登录的操做,可是登录页面是security给咱们默认的页面。

想使用咱们自定义的页面,security继续替咱们认证,在core模块下的resource文件下建立public目录后建立咱们自定义的login.html登录页面

<!DOCTYPE HTML>
<html>
<head>
    <title>登录页面</title>
</head>

<body>
<h2>表单登陆</h2>
<form action="/core/login" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td>
                <input type="text" placeholder="用户名" name="username" required="required" />
            </td>
        </tr>
        <tr>
            <td>密码:</td>
            <td>
                <input type="password" placeholder="密码" name="password" required="required" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登陆</button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>
复制代码

要注意form表单中的action属性,这里的值要与后面在security配置类中的loginProcessingUrl("/login")相同,我写成/core/login是由于我配置了context-path,若是你没配置不用写/core前缀

访问的index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>

登录了!!!

</body>
</html>
复制代码

修改security配置类的configure(HttpSecurity httpSecurity)方法

/** * 配置如何经过拦截器保护咱们的请求,哪些能经过哪些不能经过,容许对特定的http请求基于安全考虑进行配置 * @param httpSecurity http * @throws Exception 异常 */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 暂时禁用csrc不然没法提交
                .csrf().disable()
                // 设置最多一个用户登陆,若是第二个用户登录则第一用户被踢出,并跳转到登录页面
                .sessionManagement().maximumSessions(1).expiredUrl("/login.html");
        httpSecurity
                // 开始认证
                .authorizeRequests()
                // 对静态文件和登录页面放行
                .antMatchers("/static/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/login.html").permitAll()
                // 其余请求须要认证登录
                .anyRequest().authenticated();
        httpSecurity
                // 表单登录
                .formLogin()
                // 设置跳转的登录页面
                .loginPage("/login.html")
                // .failureUrl("/auth/login?error") 设置若是出错跳转到哪一个页面
                // security默认使用的就是login路径认证,若是想使用自定义自行修改就能够了
                .loginProcessingUrl("/login")
                // 若是直接访问登陆页面,则登陆成功后重定向到这个页面,不然跳转到以前想要访问的页面
                .defaultSuccessUrl("/index.html");
    }
复制代码

loginPage()中也能够填写接口路径,经过接口来返回登录页面;经过以上代码实现了咱们本身定义的登录页面,security会为咱们拦截并认证的,只是建立了两个页面和增长了两个属性。

想要自定义实现登录成功的逻辑,能够配置successHandler()方法,要实现的接口为AuthenticationSuccessHandler,重写其中的方法为onAuthenticationSuccess()。

自定义退出

想要实现自定义退出功能的逻辑,须要实现AuthenticationFailureHandler 接口,重写其中的onAuthenticationFailure方法。或者在配置中的方法中写匿名内部类。

而关于security中的退出,也有给咱们默认实现,经过“/logout”就能够实现退出功能了,在index.html界面添加一个 a 标签便可。

<a href="/core/logout">退出</a>
复制代码

再次提示我这里写/core是由于我配置了context-path,若是你没有配置直接写/logout便可,默认security机制会进行以下操做:

  • 使HttpSession无效
  • 清理记住密码
  • 清理SecurityContextHolder
  • 重定向/login?logout

固然咱们也能够自定义退出,在configure(HttpSecurity httpSecurity)方法中添加如下内容,这里我只实现了最简单的操做

httpSecurity
                // 登出
                .logout()
                // 登出处理,使用security默认的logout,也能够自定义路径,实现便可
                .logoutUrl("/logout")
                // 登出成功后跳转到哪一个页面
                .logoutSuccessUrl("/login.html")
                .logoutSuccessHandler((request, response, authentication) -> {
                    //登出成功处理函数
                    System.out.println("logout success");
                    response.sendRedirect("/core/login.html");
                })
                .addLogoutHandler((request, response, authentication) ->{
                    //登出处理函数
                    System.out.println("logout------");
                })
                // 清理Session
                .invalidateHttpSession(true);
复制代码

关于security上面咱们实现了最基本的功能,它还能够作更多的事情,好比记住个人密码,第三方登陆等等。

相关文章
相关标签/搜索