Java学习笔记-Day88 Spring Security安全框架


一、Spring Security的简介


Spring Security正是Spring 家族中的成员,Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

应用的安全性包括 用户认证(Authentication)用户授权(Authorization) 两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

Spring Security的主要核心功能为认证和授权,整个架构也是基于这两个核心功能去实现的。

二、Spring Security和Shiro比较


Spring Security 的特点:

(1)和Spring无缝整合。
(2)全面的权限控制。
(3)专门为Web开发而设计。
(4)旧版本不能脱离Web环境使用。
(5)新版本对整个框架进行了分层抽取,分成了核心模块和Web 模块。单独引入核心模块就可以脱离Web环境。
(6)重量级。

Shiro 的特点:

(1)轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
(2)通用性。
(3)好处:不局限于Web环境,可以脱离Web环境使用。
(4)缺陷:在Web环境下一些特定的需求需要手动编写代码定制。

Shiro和Security相比,各有千秋,在 SSM 中整合 Spring Security 相对麻烦,所以 Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多,很多场景中,Shiro也够用。Spring Boot 横空出世后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。

因此,常见的安全管理技术栈的组合是这样的:

扫描二维码关注公众号,回复: 13011149 查看本文章
  • SSM + Shiro、JSP + Servlet + Shiro
  • Spring Boot + Spring Security、Spring Cloud + Spring Security
  • Spring Boot + Shiro +Jwt、Spring Cloud + Shiro +Jwt

三、Spring Security的使用


只导入Spring Security 依赖,不进行任何配置的话,登录默认使用的用户名是 user, 密码则是随机生成并使用UUID加密后的密码(会在控制台输出)。这里我们会修改成使用数据库中用户表的用户名和密码进行登录,数据库表中的密码存储的是使用UUID加密后的密码。

步骤:

(1)点击 New Project -> 选择Spring Initializr -> 点击Next -> 输入Group和Artifact,Java Version选择8 ->点击Next。

在这里插入图片描述
(2)选择Web的Spring Web、Security的Spring Security、SQL的Mybatis Framework和MySQL Driver。

在这里插入图片描述

(3)输入Project name和Project location,点击 Finish 完成。

在这里插入图片描述
(4)修改配置文件application.properties。

server.port=8081
logging.level.org.springframework.security=trace

#数据库连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zmalldb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#指定mybatis中配置的映射文件的地址
mybatis.mapper-locations=classpath:/mapper/*.xml

(5)将idea连接MySQL数据库,点击idea右侧的Database -> 点击 + 号 -> 选择Data Source -> 选择MySQL。

在这里插入图片描述
在General选项页输入IP地址、端口号、账号、密码、数据库名。点击Test Connecton,会出现时区错误,点击set time zone设置时区。将servertimezone修改为Asia/Shanghai。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

添加成功后,会在右侧显示如下界面。

在这里插入图片描述

(6)选择tbl_user表右键点击,使用 MyBatisX-Generator 插件生成tbl_user表的entity类和Mapper类。在 LoginUserMapper.java 和 LoginUserMapper.xml 中创建selectByName方法。注意:用户表中一定要有用户名和用户密码这两个字段。

在这里插入图片描述
在这里插入图片描述

  • LoginUser.java
package com.etc.securitydemo.entity;

import java.io.Serializable;

/**
 * null
 * @TableName tbl_user
 */
public class LoginUser implements Serializable {
    
    
    /**
     * 序号
     */
    private Integer userid;

    /**
     * 账号
     */
    private String username;

    /**
     * 密码
     */
    private String userpwd;

    /**
     * 
     */
    private String usersex;

    /**
     * 电话
     */
    private String userphone;

    /**
     * 0表示异常/1表示正常
     */
    private Integer userstate;

    /**
     * 收货地址
     */
    private String address;

    private static final long serialVersionUID = 1L;

    /**
     * 序号
     */
    public Integer getUserid() {
    
    
        return userid;
    }

    /**
     * 序号
     */
    public void setUserid(Integer userid) {
    
    
        this.userid = userid;
    }

    /**
     * 账号
     */
    public String getUsername() {
    
    
        return username;
    }

    /**
     * 账号
     */
    public void setUsername(String username) {
    
    
        this.username = username;
    }

    /**
     * 密码
     */
    public String getUserpwd() {
    
    
        return userpwd;
    }

    /**
     * 密码
     */
    public void setUserpwd(String userpwd) {
    
    
        this.userpwd = userpwd;
    }

    /**
     */
    public String getUsersex() {
    
    
        return usersex;
    }

    /**
     */
    public void setUsersex(String usersex) {
    
    
        this.usersex = usersex;
    }

    /**
     * 电话
     */
    public String getUserphone() {
    
    
        return userphone;
    }

    /**
     * 电话
     */
    public void setUserphone(String userphone) {
    
    
        this.userphone = userphone;
    }

    /**
     * 0表示异常/1表示正常
     */
    public Integer getUserstate() {
    
    
        return userstate;
    }

    /**
     * 0表示异常/1表示正常
     */
    public void setUserstate(Integer userstate) {
    
    
        this.userstate = userstate;
    }

    /**
     * 收货地址
     */
    public String getAddress() {
    
    
        return address;
    }

    /**
     * 收货地址
     */
    public void setAddress(String address) {
    
    
        this.address = address;
    }

    @Override
    public boolean equals(Object that) {
    
    
        if (this == that) {
    
    
            return true;
        }
        if (that == null) {
    
    
            return false;
        }
        if (getClass() != that.getClass()) {
    
    
            return false;
        }
        LoginUser other = (LoginUser) that;
        return (this.getUserid() == null ? other.getUserid() == null : this.getUserid().equals(other.getUserid()))
            && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
            && (this.getUserpwd() == null ? other.getUserpwd() == null : this.getUserpwd().equals(other.getUserpwd()))
            && (this.getUsersex() == null ? other.getUsersex() == null : this.getUsersex().equals(other.getUsersex()))
            && (this.getUserphone() == null ? other.getUserphone() == null : this.getUserphone().equals(other.getUserphone()))
            && (this.getUserstate() == null ? other.getUserstate() == null : this.getUserstate().equals(other.getUserstate()))
            && (this.getAddress() == null ? other.getAddress() == null : this.getAddress().equals(other.getAddress()));
    }

    @Override
    public int hashCode() {
    
    
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getUserid() == null) ? 0 : getUserid().hashCode());
        result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
        result = prime * result + ((getUserpwd() == null) ? 0 : getUserpwd().hashCode());
        result = prime * result + ((getUsersex() == null) ? 0 : getUsersex().hashCode());
        result = prime * result + ((getUserphone() == null) ? 0 : getUserphone().hashCode());
        result = prime * result + ((getUserstate() == null) ? 0 : getUserstate().hashCode());
        result = prime * result + ((getAddress() == null) ? 0 : getAddress().hashCode());
        return result;
    }

    @Override
    public String toString() {
    
    
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", userid=").append(userid);
        sb.append(", username=").append(username);
        sb.append(", userpwd=").append(userpwd);
        sb.append(", usersex=").append(usersex);
        sb.append(", userphone=").append(userphone);
        sb.append(", userstate=").append(userstate);
        sb.append(", address=").append(address);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}
  • LoginUserMapper.java
package com.etc.securitydemo.mapper;

import com.etc.securitydemo.entity.LoginUser;

/**
 * @Entity com.etc.securitydemo.entity.LoginUser
 */
public interface LoginUserMapper {
    
    

    int deleteByPrimaryKey(Long id);

    int insert(LoginUser record);

    int insertSelective(LoginUser record);

    LoginUser selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(LoginUser record);

    int updateByPrimaryKey(LoginUser record);

    LoginUser selectByName(String username);
}
  • LoginUserMapper.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.etc.securitydemo.mapper.LoginUserMapper">

    <resultMap id="BaseResultMap" type="com.etc.securitydemo.entity.LoginUser">
            <id property="userid" column="userid" jdbcType="INTEGER"/>
            <result property="username" column="username" jdbcType="VARCHAR"/>
            <result property="userpwd" column="userpwd" jdbcType="VARCHAR"/>
            <result property="usersex" column="usersex" jdbcType="CHAR"/>
            <result property="userphone" column="userphone" jdbcType="VARCHAR"/>
            <result property="userstate" column="userstate" jdbcType="INTEGER"/>
            <result property="address" column="address" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        userid,username,userpwd,
        usersex,userphone,userstate,
        address
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from tbl_user
        where  userid = #{
    
    userid,jdbcType=INTEGER} 
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from tbl_user
        where  userid = #{
    
    userid,jdbcType=INTEGER} 
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.LoginUser" useGeneratedKeys="true">
        insert into tbl_user
        ( userid,username,userpwd
        ,usersex,userphone,userstate
        ,address)
        values (#{
    
    userid,jdbcType=INTEGER},#{
    
    username,jdbcType=VARCHAR},#{
    
    userpwd,jdbcType=VARCHAR}
        ,#{
    
    usersex,jdbcType=CHAR},#{
    
    userphone,jdbcType=VARCHAR},#{
    
    userstate,jdbcType=INTEGER}
        ,#{
    
    address,jdbcType=VARCHAR}))
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.LoginUser" useGeneratedKeys="true">
        insert into tbl_user
        <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="userid != null">userid,</if>
                <if test="username != null">username,</if>
                <if test="userpwd != null">userpwd,</if>
                <if test="usersex != null">usersex,</if>
                <if test="userphone != null">userphone,</if>
                <if test="userstate != null">userstate,</if>
                <if test="address != null">address,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="userid != null">userid = #{
    
    userid,jdbcType=INTEGER},</if>
                <if test="username != null">username = #{
    
    username,jdbcType=VARCHAR},</if>
                <if test="userpwd != null">userpwd = #{
    
    userpwd,jdbcType=VARCHAR},</if>
                <if test="usersex != null">usersex = #{
    
    usersex,jdbcType=CHAR},</if>
                <if test="userphone != null">userphone = #{
    
    userphone,jdbcType=VARCHAR},</if>
                <if test="userstate != null">userstate = #{
    
    userstate,jdbcType=INTEGER},</if>
                <if test="address != null">address = #{
    
    address,jdbcType=VARCHAR},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.etc.securitydemo.entity.LoginUser">
        update tbl_user
        <set>
                <if test="username != null">
                    username = #{
    
    username,jdbcType=VARCHAR},
                </if>
                <if test="userpwd != null">
                    userpwd = #{
    
    userpwd,jdbcType=VARCHAR},
                </if>
                <if test="usersex != null">
                    usersex = #{
    
    usersex,jdbcType=CHAR},
                </if>
                <if test="userphone != null">
                    userphone = #{
    
    userphone,jdbcType=VARCHAR},
                </if>
                <if test="userstate != null">
                    userstate = #{
    
    userstate,jdbcType=INTEGER},
                </if>
                <if test="address != null">
                    address = #{
    
    address,jdbcType=VARCHAR},
                </if>
        </set>
        where   userid = #{
    
    userid,jdbcType=INTEGER} 
    </update>
    <update id="updateByPrimaryKey" parameterType="com.etc.securitydemo.entity.LoginUser">
        update tbl_user
        set 
            username =  #{
    
    username,jdbcType=VARCHAR},
            userpwd =  #{
    
    userpwd,jdbcType=VARCHAR},
            usersex =  #{
    
    usersex,jdbcType=CHAR},
            userphone =  #{
    
    userphone,jdbcType=VARCHAR},
            userstate =  #{
    
    userstate,jdbcType=INTEGER},
            address =  #{
    
    address,jdbcType=VARCHAR}
        where   userid = #{
    
    userid,jdbcType=INTEGER} 
    </update>

    <select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from tbl_user
        where  username = #{
    
    username,jdbcType=VARCHAR}
    </select>
</mapper>

(7)在 src\test\java\com\etc\securitydemo 目录下创建测试类来测试Mapper类。输入encode变量是使用UUID加密后的字符串。

package com.etc.securitydemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootTest
class SecuritydemoApplicationTests {
    
    
    @Test
    void contextLoads() {
    
    
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode("123456");
        System.out.println(encode);
        boolean matches = passwordEncoder.matches("123456", encode);
        System.out.println(matches);
    }
}

在这里插入图片描述
(8)创建config包,并在该包下创建SecurityConfig类。(重要)

package com.etc.securitydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {
    
    
    @Bean
    public PasswordEncoder getPasswordEncoder(){
    
    
        return new BCryptPasswordEncoder();
    }
}

(9)创建service包,并在该包下创建UserDetailServiceImpl实现类。(重要)

package com.etc.securitydemo.service;

import com.etc.securitydemo.entity.LoginUser;
import com.etc.securitydemo.mapper.LoginUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service("userDetailServiceImpl")
public class UserDetailServiceImpl implements UserDetailsService {
    
    
    @Autowired
    private LoginUserMapper loginUserMapper;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
    
        LoginUser loginUser = loginUserMapper.selectByName(s);
        if(loginUser == null){
    
    
            throw new UsernameNotFoundException("用户不存在!");
        }
        User user = new User(loginUser.getUsername(),loginUser.getUserpwd(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        return user;
    }
}

(10)创建controller包,并在该包下创建Controller类。

package com.etc.securitydemo.controller;

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

@Controller
public class HelloController {
    
    

    @GetMapping("hello")
    @ResponseBody
    public String GetHello(){
    
    
        return "Hello,Spring Security!";
    }
}

(11)在SecuritydemoApplication运行类前添加@MapperScan注解。

package com.etc.securitydemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.etc.securitydemo.mapper")
public class SecuritydemoApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(SecuritydemoApplication.class, args);
    }

}

(12)运行SecuritydemoApplication启动类。访问网页 127.0.0.1:8081/hello,输入数据库中用户表的用户称和密码进行登录。注意:数据库用户表中的密码存储的是使用UUID加密后的密码。

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42141141/article/details/115312126