SpringSecurity菜鸟教程

一:简单配置权限管理

SecurityConfg的配置

package com.example.demo11.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Objects;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return Objects.equals(charSequence.toString(), s);
            }
        };
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("用户").password("123").roles("vip1")
                .and()
                .withUser("管理员").password("123").roles("vip2")
                .and()
                .withUser("超级管理员").password("123").roles("vip1", "vip2");
    }

    //配置忽略掉的 URL 地址,一般用于js,css,图片等静态资源
    @Override
    public void configure(WebSecurity web) throws Exception {
        //web.ignoring() 用来配置忽略掉的 URL 地址,一般用于静态文件
        web.ignoring().antMatchers("/js/**", "/css/**", "/fonts/**", "/images/**", "/lib/**");
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/css/**", "/js/**", "/images/**").permitAll();
        //开启运行iframe嵌套页面
        http.headers().frameOptions().disable();

        http.authorizeRequests()
                .antMatchers("/level1/vip1").hasRole("vip1")
                .antMatchers("/level2/vip2").hasRole("vip2");


        //没有权限会到默认的登录页面
        http.formLogin();



    }
}

IndexController的代码

package com.example.demo11.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    @GetMapping("/index")
    public String index(){
        return "index";
    }

    @GetMapping("/level1/vip1")
    public String level1Vip1(){
        return "level1/vip1";
    }
    @GetMapping("/level2/vip2")
    public String level2Vip1(){
        return "level2/vip2";
    }
}

由于没有设置springsecurity全部拦截,主页可以允许所有人访问

springEL菜鸟 spring security菜鸟教程_ide

二:自定义登录页面,记住密码

1自定义登陆页面
改变SecurityConfig中的配置
这个需要自己写一个登录的接口

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/css/**", "/js/**", "/images/**").permitAll();
        //开启运行iframe嵌套页面
        http.headers().frameOptions().disable();

        http.authorizeRequests()
                .antMatchers("/level1/vip1").hasRole("vip1")
                .antMatchers("/level2/vip2").hasRole("vip2");

        //任何请求都必须经过身份认证
        http.authorizeRequests().anyRequest().authenticated();
        //没有权限会到默认的登录页面
        http.formLogin()
                //登录的页面
                .loginPage("/login")
                .usernameParameter("username")//自定义表单的用户名的name,默认为username
                .passwordParameter("password")//自定义表单的密码的name,默认为password
                .loginProcessingUrl("/dologin")//表单请求的地址,一般与form的action属性一致,注意:不用自己写doLogin接口,只要与form的action属性一致即可
                .successForwardUrl("/index")//登录成功后跳转的页面(重定向)
                .failureForwardUrl("/login")//登录失败后跳转的页面(重定向)
                .and()
                .logout() //开启注销功能
                .logoutSuccessUrl("/login") //注销后跳转到哪一个页面
                .clearAuthentication(true)// 配置注销登录请求URL为"/logout"(默认也就是 /logout)
                .clearAuthentication(true) // 清除身份认证信息
                .invalidateHttpSession(true) //使Http会话无效
                .permitAll()
                .and().csrf().disable();



    }
login.html文件

springEL菜鸟 spring security菜鸟教程_springEL菜鸟_02

2.记住密码和注销功能

//开启记住我功能,cookie接收,默认保存两周,自定义接收其前端
        http.rememberMe().rememberMeParameter("remember");

springEL菜鸟 spring security菜鸟教程_ide_03


注销功能:

springEL菜鸟 spring security菜鸟教程_springEL菜鸟_04

三:基于数据库自定义的表单验证

1.数据库表
这里的登录认证只涉及到三张表:用户表(user)、角色表(role)、用户角色中间表(user_role)。

/*
 Navicat Premium Data Transfer

 Source Server         : test3
 Source Server Type    : MySQL
 Source Server Version : 80015
 Source Host           : localhost:3306
 Source Schema         : test2

 Target Server Type    : MySQL
 Target Server Version : 80015
 File Encoding         : 65001

 Date: 31/05/2020 22:01:56
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_vip0');
INSERT INTO `role` VALUES (2, 'ROLE_vip1');
INSERT INTO `role` VALUES (3, 'ROLE_vip2');
INSERT INTO `role` VALUES (4, 'ROLE_vip3');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (3, '灰太狼', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (4, '喜羊羊', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (5, '懒羊羊', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (6, '小灰灰', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NULL DEFAULT NULL,
  `rid` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 1, 3);
INSERT INTO `user_role` VALUES (4, 1, 4);
INSERT INTO `user_role` VALUES (5, 3, 2);
INSERT INTO `user_role` VALUES (6, 4, 3);
INSERT INTO `user_role` VALUES (7, 6, 4);
INSERT INTO `user_role` VALUES (8, 5, 1);

SET FOREIGN_KEY_CHECKS = 1;

springEL菜鸟 spring security菜鸟教程_springEL菜鸟_05

注意:这里的role跟上面的例子相比多加了ROLE_前缀。这是因为之前的role都是通过springsecurity的api赋值过去的,他会自行帮我们加上这个前缀。但是现在我们使用的是自己的数据库里面读取出来的权限,然后封装到自己的实体类中。所以这时候需要我们自己手动添加这个ROLE_前缀。经过测试如果不加ROLE_前缀的话,可以做数据库的认证,但无法做授权

2.建实体类User,注意User需要实现UserDetails接口,并且实现该接口下的7个接口

package com.example.demo11.pojo;

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {
    private Integer id;
    private String userName;
    private String passWord;

    private List<Role> roles;//该用户对应的角色

    /**
     * 返回用户的权限集合。
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles){
            authorities.add(new SimpleGrantedAuthority(role.getName()));
            System.out.println(authorities);
        }
        return authorities;
    }

    /**
     * 返回账号的密码
     * @return
     */
    @Override
    public String getPassword() {
        return passWord;
    }

    /**
     * 返回账号的用户名
     * @return
     */
    @Override
    public String getUsername() {
        return userName;
    }

    /**
     * 账号是否失效,true:账号有效,false账号失效。
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }


    /**
     * 账号是否被锁,true:账号没被锁,可用;false:账号被锁,不可用
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 账号认证是否过期,true:没过期,可用;false:过期,不可用
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 账号是否可用,true:可用,false:不可用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

角色表实体类Role,这个类不用实现上述接口

package com.zsc.po;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
    private Integer id;
    private String name;//角色的名字
}

接下来做数据库的查询,创建持久层接口(UserMapper和RoleMapper)

package com.example.demo.mapper;


import com.example.demo.pojo.Role;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface RoleMapper {
    /**
     * 通过用户id获取用户角色集合
     *
     * @param userId 用户id
     * @return List<Role> 角色集合
     */
    List<Role> getRolesByUserId(Integer userId);

}
package com.example.demo.mapper;


import com.example.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface UserMapper {
    /**
     * 通过用户名获取用户信息
     *
     * @param username 用户名
     * @return User 用户信息
     */
    List<User> getUserByUsername(String username);

}

持久层接口对应配置文件(UserMapper.xml和RoleMapper.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.demo.mapper.RoleMapper">
    <resultMap id="roleMap" type="com.example.demo.pojo.Role">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
    </resultMap>

    <select id="getRolesByUserId" resultMap="roleMap">
        select * from role r,user_role ur where r.id = ur.rid and ur.uid = #{userId}
    </select>

</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.demo.mapper.UserMapper">
    <resultMap id="userMap" type="com.example.demo.pojo.User">
        <id column="id" property="id"></id>
        <result column="username" property="userName"></result>
        <result column="password" property="passWord"></result>
        <collection property="roles" ofType="com.example.demo.pojo.Role">
            <id property="id" column="rid"></id>
            <result column="rname" property="name"></result>
        </collection>
    </resultMap>

    <select id="getUserByUsername" resultMap="userMap">
        select * from user  where username = #{username}
    </select>
</mapper>