SpringSecurity基本搭建

Spring Security项目搭建

Spring-Securityspring提供的安全框架,基于url实现,这一点和shiro类似,在ssm项目中配置比较繁琐,一般它用在springbootspringcloud项目中

1. 准备一个web项目

添加测试接口

并测试项目没有问题后添加maven节点

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 配置springsecurity

添加springsecurity的配置文件SecurityConfig.java,并继承WebSecurityConfigurerAdapter

 

先看看WebSecurityConfigurerAdapter的方法

 

方法有很多重点是红框框起来的三个方法,我们需要重写他们

第一个方法:

/**
 * 配置用户
 *
 * @param auth
 * @throws Exception
 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    super.configure(auth);
}

从入参不难看出,这个方法是对用户进行配置的,也就是说你需要对什么样的用户进行安全管理,是基于内存还是数据库?都可以在这里配置

第二个方法:

/**
 * 配置web的
 *
 * @param web+
 * @throws Exception
 */
@Override
public void configure(WebSecurity web) throws Exception {
    super.configure(web);
}

这个方法同样不难发现是基于web的配置

第三个方法:

/**
 * 配置安全
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
}

这个就是安全相关的配置

下边简单写一个配置来看看效果

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 密码比对器
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()//基于内存的用户和密码
                .withUser("lhf").password(passwordEncoder().encode("123456")).roles("admin");

    }


    /**
     * 配置web的
     *
     * @param web+
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }


    /**
     * 配置安全
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.authorizeRequests().anyRequest().authenticated()//所有请求都必须登录
.and()
.formLogin()//开启表单登录
.permitAll()
.and()
.csrf().disable()//关闭防csrf攻击
.cors()
;
    }
}

这时请求http://localhost:8080/hello 会被强行拦截到springsecurity的内置的登录页面,然后输入用户名和密码进行登录即可。

一个简单的登录就告一段落,下边一滴一滴的开始配置他。

3. 准备数据库

众所周知的,权限这一块的数据库是一般分为5个表:用户、角色、用户角色、权限、角色权限。(严格来说应该是是哪个表,但是他们之间有事多对多关系所以说多了用户角色表,和角色权限表,两个中间表)

用户表:

CREATE TABLE `sys_user` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

  `username` varchar(50) NOT NULL COMMENT '用户名',

  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码',

  `real_name` varchar(20) NOT NULL COMMENT '真实姓名',

  `email` varchar(50) DEFAULT NULL COMMENT '电子邮件',

  `head_img` varchar(255) DEFAULT NULL COMMENT '头像',

  `phone` varchar(11) DEFAULT NULL COMMENT '联系方式',

  `is_lock` int(1) DEFAULT '0' COMMENT '是否锁定 0未锁定 1已锁定',

  `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

  `create_time` datetime DEFAULT NULL COMMENT '创建时间',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

角色表:

CREATE TABLE `sys_role` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

  `role_name` varchar(10) NOT NULL COMMENT '角色名',

  `role` varchar(20) NOT NULL COMMENT '角色',

  `role_ico` varchar(50) DEFAULT NULL COMMENT '角色图标',

  `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

用户角色关联表:

CREATE TABLE `sys_user_role` (

  `user_id` int(11) NOT NULL COMMENT '用户id',

  `role_id` int(11) NOT NULL COMMENT '角色id',

  UNIQUE KEY `uid_rid_index` (`user_id`,`role_id`) USING BTREE,

  KEY `user_role_rid` (`role_id`),

  CONSTRAINT `user_role_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,

  CONSTRAINT `user_role_uid` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

权限表:

CREATE TABLE `sys_per` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

  `per_name` varchar(10) NOT NULL COMMENT '权限名字',

  `per` varchar(20) NOT NULL COMMENT '权限',

  `per_ico` varchar(50) DEFAULT NULL COMMENT '权限图标',

  `compoment` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '组件名字',

  `path` varchar(50) DEFAULT NULL COMMENT '请求路径',

  `type` int(1) DEFAULT NULL COMMENT '0 菜单 1按钮',

  `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父类id',

  `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

角色权限关联表:

CREATE TABLE `sys_role_per` (

  `role_id` int(11) NOT NULL COMMENT '角色id',

  `per_id` int(11) NOT NULL COMMENT '权限id',

  UNIQUE KEY `rid_pid_index` (`role_id`,`per_id`) USING BTREE,

  KEY `role_per_pid` (`per_id`),

  CONSTRAINT `role_per_pid` FOREIGN KEY (`per_id`) REFERENCES `sys_per` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,

  CONSTRAINT `role_per_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

这样数据库就准备好了(后边就是正常的springboot+mybatis的配置省略)

4. UserDetails

springsecurityuserdetails是一个重要的对象,他是一个接口,提供用户的信息(包括用户名、密码、权限等).框架提供了默认的实现User,但是往往他是不能满足我们的需求,或者说不方便,于是可以自定义一个实现

package com.lhf.springsecurity.entity;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Date;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;

/**
 * (SysUser)实体类
 *
 * @author 刘会发
 * @since 2020-05-03 09:28:53
 */
@Data
public class SysUser implements Serializable, UserDetails {
    private static final long serialVersionUID = -13438204047342005L;
    /**
     * 主键
     */
    private Integer uid;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 真实姓名
     */
    private String realName;
    /**
     * 电子邮件
     */
    private String email;
    /**
     * 头像
     */
    private String headImg;
    /**
     * 联系方式
     */
    private String phone;
    /**
     * 是否锁定 0未锁定 1已锁定
     */
    private Integer isLock;
    /**
     * 0未删除 1已删除
     */
    private Integer isDel;
    /**
     * 创建时间
     */
    private Date createTime;

    private List<SysRole> roles;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
       return roles.stream().flatMap(item -> item.getPers().stream().map(per -> new SimpleGrantedAuthority(per.getPer()))).collect(Collectors.toSet());

    }


    @Override
    public boolean isAccountNonExpired() {
        return true;
    }


    @Override
    public boolean isAccountNonLocked() {
        return this.isLock == 0;
    }


    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }


    @Override
    public boolean isEnabled() {
        return true;
    }
}

其中GrantedAuthority 是保存用户角色信息的对象,如果需要用权限控制的话可以修改这里的实现方式。

5. UserDetailsService

UserDetailsService 是一个接口,他是提供用户信息的接口,他的返回值就是一个UserDetails

所以代码这样写:

package com.lhf.springsecurity.service;

import com.lhf.springsecurity.entity.SysUser;
import org.springframework.security.core.userdetails.UserDetailsService;

import java.util.List;

/**
 * (SysUser)表服务接口
 *
 * @author 刘会发
 * @since 2020-05-03 09:28:53
 */
public interface SysUserService extends UserDetailsService {


}

实现类:

package com.lhf.springsecurity.service.impl;

import com.lhf.springsecurity.entity.SysUser;
import com.lhf.springsecurity.dao.SysUserDao;
import com.lhf.springsecurity.service.SysUserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * (SysUser)表服务实现类
 *
 * @author 刘会发
 * @since 2020-05-03 09:28:53
 */
@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
    @Resource
    private SysUserDao sysUserDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserDao.login(username);
        if (user == null)
            throw new UsernameNotFoundException("用户未找到");
        return user;
    }
}

 

通过数据库查询出SysUser对象(前边实现UserDetails),并将他返回。

到这里用户信息提供者也配置好了,剩下的只需要简单配置springsecurity即可

/**
 * 配置用户
 *
 * @param auth
 * @throws Exception
 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(passwordEncoder());//配置用户提供者,并配置密码比对器
}

现在请求http://localhost:8080/hello 并登陆

6. 权限控制

代码配置

主要是在SecurityConfig中进行配置

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/hello").hasAuthority("ADMIN")

 .antMatchers("/look").hasAuthority("LOOK")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

.antMatchers("/hello").hasAuthority("ADMIN") 拥有ADMIN角色才能访问 /hello接口

这时登陆后分别访问http://localhost:8080/look;http://localhost:8080/hello

/look 接口将不会返回任何信息,还会报错

SpringSecurity通过url控制权限,自然和shiro一样,他们是有先后顺序的如果这样

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/**").permitAll()//所有接口给予任何权限
            .antMatchers("/hello").hasAuthority("PER_SYSTEM")
            .antMatchers("/look").hasAuthority("LOOK")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

在最前边给了所有路径任何权限,那么下边的配置将不会生效

.antMatchers("/**").permitAll(); 给任何请求路径所有的权限

注解配置

自然springsecurity也是支持注解配置的但是需要开启自动配置

@EnableGlobalMethodSecurity(prePostEnabled = true)

可以把它添加到SecurityConfig上,也可以添加到启动类上

添加权限注解

/**
 * <p></p>
 *
 * @author zy 刘会发
 * @version 1.0
 * @since 2020/5/3
 */
@RestController
public class TestController {

    @PreAuthorize("hasAnyAuthority('PER_SYSTEM')")
    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }

    @RequestMapping("look")
    @PreAuthorize("hasAnyAuthority('LOOK')")
    public String look() {
        return "look";
    }
}

这样在登录admin,分别访问两个接口

7. 自定义登录页面

当然,spring官方给的登录页面略微丑陋,接下来看看如何自定义登录页面

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")//自定义登录页面的url
            .loginProcessingUrl("/login")//登录请求的url
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

这里我自己准备了一个登录页面放在了static目录下

页面的表单:

<div class="form">
    <div>
        <span>登录系统</span>
        <form id="form" action="/login" method="post">
            <label>
                用户名:
                <input name="username" type="text"/>
            </label>
            <label>
                密   码:
                <input name="password" type="password">
            </label>
            <input type="submit" class="submit" value="登录"/>
        </form>
    </div>
</div>

 

重新启动项目,随便请求一个接口会跳转到该页面

8. 自定义登录返回

自定义登录成功的返回:

实现AuthenticationSuccessHandler接口,并重写onAuthenticationSuccess方法

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();

//authentication 存放的是该用户的所有信息
        writer.write("hello,you are success:"+authentication.getName());

        writer.flush();
        writer.close();
    }
}

将他添加到springsecurity:

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")//自定义登录页面的url
            .loginProcessingUrl("/login")//登录请求的url
            .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

这里直接返回一句话,用postman进行测试:

自定义登录失败的返回

实现AuthenticationFailureHandler接口,重写onAuthenticationFailure

public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("hello,you are failure:" + e.getMessage());
        writer.flush();
        writer.close();
    }
}

自定义未授权返回

实现AccessDeniedHandler接口,重写handle方法

自定义未登录时返回

实现AuthenticationEntryPoint接口,重写commence方法

 

/**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")//自定义登录页面的url
                .loginProcessingUrl("/login")//登录请求的url
                .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
//                .successForwardUrl("/index.html")//登录成功转发url
                .failureHandler(new LoginFailureHandler())//添加自定义登录失败页面
                .permitAll()
                .and()
                .exceptionHandling()
                .accessDeniedHandler((request, response, e) -> {
//                    自定义无权访问返回
                    request.setCharacterEncoding("utf-8");
                    response.setCharacterEncoding("utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write("hello,you don't have permission");
                    writer.flush();
                    writer.close();
                })
                .authenticationEntryPoint((request, response, e) -> {
//                    自定义未登录时返回
                    request.setCharacterEncoding("utf-8");
                    response.setCharacterEncoding("utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write("hello,you need login");
                    writer.flush();
                    writer.close();
                })
                .and()
                .csrf().disable()
                .cors().disable();
    }

熟悉jdk1.8的都知道,一个接口有且只有一个方法的时候,可以使用lamda表达式,上诉代码中自定义未授权返回和自定义未登录时返回采用lamda表达式,当然登陆成功和登录失败也同样可以这样(不建议,项目比较大,而自定义返回逻辑复杂时,这个配置类将会十分的臃肿,这里只是为了方便)

9. 记住我

springsecurity原生支持记住我的功能,有基于内存的、还有基于数据库的

 

 /**
     * 记住我功能,通过数据库
     *
     * @return
     */
    @Bean
    PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl impl = new JdbcTokenRepositoryImpl();
        impl.setDataSource(dataSource);
        impl.afterPropertiesSet();
//        impl.setCreateTableOnStartup(true); //第一次启动开启,将会创建表,之后直接关闭即可
        return impl;
    }

这里配置一个基于数据库的实现

impl.setCreateTableOnStartup(true) 可以生成默认表结构,在第一次启动时将参数设置为true,以后将不再需要,注释即可

 

项目地址:

https://github.com/Liuhuifa/spring-security

Sql文件在项目resources目录下

Sql文件中有一些没用的表,自行忽略

Spring Security项目搭建

Spring-Securityspring提供的安全框架,基于url实现,这一点和shiro类似,在ssm项目中配置比较繁琐,一般它用在springbootspringcloud项目中

1. 准备一个web项目

添加测试接口

 

并测试项目没有问题后添加maven节点

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 配置springsecurity

添加springsecurity的配置文件SecurityConfig.java,并继承WebSecurityConfigurerAdapter

 

 

先看看WebSecurityConfigurerAdapter的方法

 

 

方法有很多重点是红框框起来的三个方法,我们需要重写他们

第一个方法:

/**
 * 配置用户
 *
 * @param auth
 * @throws Exception
 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    super.configure(auth);
}

从入参不难看出,这个方法是对用户进行配置的,也就是说你需要对什么样的用户进行安全管理,是基于内存还是数据库?都可以在这里配置

第二个方法:

/**
 * 配置web
 *
 * @param web+
 * @throws Exception
 */
@Override
public void configure(WebSecurity web) throws Exception {
    super.configure(web);
}

 

这个方法同样不难发现是基于web的配置

 

第三个方法:

/**
 * 配置安全
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
}

这个就是安全相关的配置

 

下边简单写一个配置来看看效果

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 密码比对器
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()//基于内存的用户和密码
                .withUser("lhf").password(passwordEncoder().encode("123456")).roles("admin");

    }


    /**
     * 配置web
     *
     * @param web+
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }


    /**
     * 配置安全
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.authorizeRequests().anyRequest().authenticated()//所有请求都必须登录
.and()
.formLogin()//开启表单登录
.permitAll()
.and()
.csrf().disable()//关闭防csrf攻击
.cors()
;
    }
}

 

这时请求http://localhost:8080/hello 会被强行拦截到springsecurity的内置的登录页面,然后输入用户名和密码进行登录即可。

一个简单的登录就告一段落,下边一滴一滴的开始配置他。

3. 准备数据库

众所周知的,权限这一块的数据库是一般分为5个表:用户、角色、用户角色、权限、角色权限。(严格来说应该是是哪个表,但是他们之间有事多对多关系所以说多了用户角色表,和角色权限表,两个中间表)

用户表:

 

CREATE TABLE `sys_user` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

  `username` varchar(50) NOT NULL COMMENT '用户名',

  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码',

  `real_name` varchar(20) NOT NULL COMMENT '真实姓名',

  `email` varchar(50) DEFAULT NULL COMMENT '电子邮件',

  `head_img` varchar(255) DEFAULT NULL COMMENT '头像',

  `phone` varchar(11) DEFAULT NULL COMMENT '联系方式',

  `is_lock` int(1) DEFAULT '0' COMMENT '是否锁定 0未锁定 1已锁定',

  `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

  `create_time` datetime DEFAULT NULL COMMENT '创建时间',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 

角色表:

 

CREATE TABLE `sys_role` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

  `role_name` varchar(10) NOT NULL COMMENT '角色名',

  `role` varchar(20) NOT NULL COMMENT '角色',

  `role_ico` varchar(50) DEFAULT NULL COMMENT '角色图标',

  `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

用户角色关联表:

 

CREATE TABLE `sys_user_role` (

  `user_id` int(11) NOT NULL COMMENT '用户id',

  `role_id` int(11) NOT NULL COMMENT '角色id',

  UNIQUE KEY `uid_rid_index` (`user_id`,`role_id`) USING BTREE,

  KEY `user_role_rid` (`role_id`),

  CONSTRAINT `user_role_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,

  CONSTRAINT `user_role_uid` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

权限表:

 

 

 

CREATE TABLE `sys_per` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

  `per_name` varchar(10) NOT NULL COMMENT '权限名字',

  `per` varchar(20) NOT NULL COMMENT '权限',

  `per_ico` varchar(50) DEFAULT NULL COMMENT '权限图标',

  `compoment` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '组件名字',

  `path` varchar(50) DEFAULT NULL COMMENT '请求路径',

  `type` int(1) DEFAULT NULL COMMENT '0 菜单 1按钮',

  `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父类id',

  `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

角色权限关联表:

 

CREATE TABLE `sys_role_per` (

  `role_id` int(11) NOT NULL COMMENT '角色id',

  `per_id` int(11) NOT NULL COMMENT '权限id',

  UNIQUE KEY `rid_pid_index` (`role_id`,`per_id`) USING BTREE,

  KEY `role_per_pid` (`per_id`),

  CONSTRAINT `role_per_pid` FOREIGN KEY (`per_id`) REFERENCES `sys_per` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,

  CONSTRAINT `role_per_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 

这样数据库就准备好了(后边就是正常的springboot+mybatis的配置省略)

 

4. UserDetails

springsecurityuserdetails是一个重要的对象,他是一个接口,提供用户的信息(包括用户名、密码、权限等).框架提供了默认的实现User,但是往往他是不能满足我们的需求,或者说不方便,于是可以自定义一个实现

package com.lhf.springsecurity.entity;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Date;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;

/**
 * (SysUser)实体类
 *
 * @author 刘会发
 * @since 2020-05-03 09:28:53
 */
@Data
public class SysUser implements Serializable, UserDetails {
    private static final long serialVersionUID = -13438204047342005L;
    /**
     * 主键
     */
    private Integer uid;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 真实姓名
     */
    private String realName;
    /**
     * 电子邮件
     */
    private String email;
    /**
     * 头像
     */
    private String headImg;
    /**
     * 联系方式
     */
    private String phone;
    /**
     * 是否锁定 0未锁定 1已锁定
     */
    private Integer isLock;
    /**
     * 0未删除 1已删除
     */
    private Integer isDel;
    /**
     * 创建时间
     */
    private Date createTime;

    private List<SysRole> roles;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
       return roles.stream().flatMap(item -> item.getPers().stream().map(per -> new SimpleGrantedAuthority(per.getPer()))).collect(Collectors.toSet());

    }


    @Override
    public boolean isAccountNonExpired() {
        return true;
    }


    @Override
    public boolean isAccountNonLocked() {
        return this.isLock == 0;
    }


    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }


    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

 

其中GrantedAuthority 是保存用户角色信息的对象,如果需要用权限控制的话可以修改这里的实现方式。

5. UserDetailsService

UserDetailsService 是一个接口,他是提供用户信息的接口,他的返回值就是一个UserDetails

所以代码这样写:

package com.lhf.springsecurity.service;

import com.lhf.springsecurity.entity.SysUser;
import org.springframework.security.core.userdetails.UserDetailsService;

import java.util.List;

/**
 * (SysUser)表服务接口
 *
 * @author 刘会发
 * @since 2020-05-03 09:28:53
 */
public interface SysUserService extends UserDetailsService {


}

实现类:

package com.lhf.springsecurity.service.impl;

import com.lhf.springsecurity.entity.SysUser;
import com.lhf.springsecurity.dao.SysUserDao;
import com.lhf.springsecurity.service.SysUserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * (SysUser)表服务实现类
 *
 * @author 刘会发
 * @since 2020-05-03 09:28:53
 */
@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
    @Resource
    private SysUserDao sysUserDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserDao.login(username);
        if (user == null)
            throw new UsernameNotFoundException("用户未找到");
        return user;
    }
}

 

通过数据库查询出SysUser对象(前边实现UserDetails),并将他返回。

到这里用户信息提供者也配置好了,剩下的只需要简单配置springsecurity即可

/**
 * 配置用户
 *
 * @param auth
 * @throws Exception
 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(passwordEncoder());//配置用户提供者,并配置密码比对器
}

 

现在请求http://localhost:8080/hello 并登陆

6. 权限控制

代码配置

主要是在SecurityConfig中进行配置

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/hello").hasAuthority("ADMIN")

 .antMatchers("/look").hasAuthority("LOOK")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

 

.antMatchers("/hello").hasAuthority("ADMIN") 拥有ADMIN角色才能访问 /hello接口

 

这时登陆后分别访问http://localhost:8080/look;http://localhost:8080/hello

/look 接口将不会返回任何信息,还会报错

 

SpringSecurity通过url控制权限,自然和shiro一样,他们是有先后顺序的如果这样

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/**").permitAll()//所有接口给予任何权限
            .antMatchers("/hello").hasAuthority("PER_SYSTEM")
            .antMatchers("/look").hasAuthority("LOOK")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

在最前边给了所有路径任何权限,那么下边的配置将不会生效

.antMatchers("/**").permitAll(); 给任何请求路径所有的权限

 

注解配置

自然springsecurity也是支持注解配置的但是需要开启自动配置

@EnableGlobalMethodSecurity(prePostEnabled = true)

可以把它添加到SecurityConfig上,也可以添加到启动类上

 

添加权限注解

/**
 * <p></p>
 *
 * @author zy 刘会发
 * @version 1.0
 * @since 2020/5/3
 */
@RestController
public class TestController {

    @PreAuthorize("hasAnyAuthority('PER_SYSTEM')")
    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }

    @RequestMapping("look")
    @PreAuthorize("hasAnyAuthority('LOOK')")
    public String look() {
        return "look";
    }
}

 

这样在登录admin,分别访问两个接口

7. 自定义登录页面

当然,spring官方给的登录页面略微丑陋,接下来看看如何自定义登录页面

 

 

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")//自定义登录页面的url
            .loginProcessingUrl("/login")//登录请求的url
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

 

这里我自己准备了一个登录页面放在了static目录下

 

页面的表单:

<div class="form">
    <div>
        <span>登录系统</span>
        <form id="form" action="/login" method="post">
            <label>
                用户名:
                <input name="username" type="text"/>
            </label>
            <label>
                   :
                <input name="password" type="password">
            </label>
            <input type="submit" class="submit" value="登录"/>
        </form>
    </div>
</div>

 

 

重新启动项目,随便请求一个接口会跳转到该页面

 

8. 自定义登录返回

自定义登录成功的返回:

实现AuthenticationSuccessHandler接口,并重写onAuthenticationSuccess方法

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();

//authentication 存放的是该用户的所有信息
        writer.write("hello,you are success:"+authentication.getName());

        writer.flush();
        writer.close();
    }
}

 

将他添加到springsecurity:

/**
 * 安全配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")//自定义登录页面的url
            .loginProcessingUrl("/login")//登录请求的url
            .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
            .permitAll()
            .and()
            .csrf().disable()
            .cors().disable();
}

 

这里直接返回一句话,用postman进行测试:

 

自定义登录失败的返回

实现AuthenticationFailureHandler接口,重写onAuthenticationFailure

public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("hello,you are failure:" + e.getMessage());
        writer.flush();
        writer.close();
    }
}

自定义未授权返回

实现AccessDeniedHandler接口,重写handle方法

自定义未登录时返回

实现AuthenticationEntryPoint接口,重写commence方法

 

 /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")//自定义登录页面的url
                .loginProcessingUrl("/login")//登录请求的url
                .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
//                .successForwardUrl("/index.html")//登录成功转发url
                .failureHandler(new LoginFailureHandler())//添加自定义登录失败页面
                .permitAll()
                .and()
                .exceptionHandling()
                .accessDeniedHandler((request, response, e) -> {
//                    自定义无权访问返回
                    request.setCharacterEncoding("utf-8");
                    response.setCharacterEncoding("utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write("hello,you don't have permission");
                    writer.flush();
                    writer.close();
                })
                .authenticationEntryPoint((request, response, e) -> {
//                    自定义未登录时返回
                    request.setCharacterEncoding("utf-8");
                    response.setCharacterEncoding("utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write("hello,you need login");
                    writer.flush();
                    writer.close();
                })
                .and()
                .csrf().disable()
                .cors().disable();
    }

 

 

熟悉jdk1.8的都知道,一个接口有且只有一个方法的时候,可以使用lamda表达式,上诉代码中自定义未授权返回和自定义未登录时返回采用lamda表达式,当然登陆成功和登录失败也同样可以这样(不建议,项目比较大,而自定义返回逻辑复杂时,这个配置类将会十分的臃肿,这里只是为了方便)

 

9. 记住我

springsecurity原生支持记住我的功能,有基于内存的、还有基于数据库的

  /**
     * 记住我功能,通过数据库
     *
     * @return
     */
    @Bean
    PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl impl = new JdbcTokenRepositoryImpl();
        impl.setDataSource(dataSource);
        impl.afterPropertiesSet();
//        impl.setCreateTableOnStartup(true); //第一次启动开启,将会创建表,之后直接关闭即可
        return impl;
    }

这里配置一个基于数据库的实现

impl.setCreateTableOnStartup(true) 可以生成默认表结构,在第一次启动时将参数设置为true,以后将不再需要,注释即可

项目地址:

https://github.com/Liuhuifa/spring-security

Sql文件在项目resources目录下

Sql文件中有一些没用的表,自行忽略

 

原文地址:https://www.cnblogs.com/Tiandaochouqin1/p/12822940.html