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。

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

  • 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加密后的密码。

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

四、自定义登录页


(1)在pom.xml加入thymeleaf模板引擎支持。

<!--加入thymeleaf模板引擎支持-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

(2)在templates目录中加入login.html和index.html文件。

  • login.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8">
	<title>后台登录</title>
</head>
<body>
    <div>
        <div>登录</div>
        <form method="post" action="/login">
            <input name="username" placeholder="用户名"  type="text"><br/>
            <input name="password" placeholder="密码"  type="password" ><br/>
            <input value="登录"  type="submit">
        </form>
    </div>
</body>
</html>
  • index.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>后台</title>
    </head>
    <body>
    <h1>首页</h1>
    </body>
</html>

(3)在application.properties中加入thymeleaf配置。

##thymeleaf配置
spring.thymeleaf.cache=false
spring.thymeleaf.suffix=.html
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.encoding=utf-8
spring.thymeleaf.mode=HTML
spring.thymeleaf.content-type=text/html

(4)创建一个LoginController文件。

package com.etc.securitydemo.controller;

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

@Controller
public class LoginController {

    @GetMapping("/tologin")
    public String toLogin() {
        return "login";
    }

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

(5)在SecurityConfig类中加入配置。登录页面为thymeleaf,不能直接访问,通过 / tologin转发,所以loginPage为/ tologin。

@Configuration
//WebSecurityConfigurerAdapter web安全配置适配器
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("userDetailServiceImpl")
    private UserDetailServiceImpl userDetailServiceImpl;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailServiceImpl);
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        //指定表单验证(formlogin)有关的内容
        http.formLogin().loginProcessingUrl("/login")//处理登录的控制器的url地址
                .defaultSuccessUrl("/toindex",true)//登录成功后默认跳转url
                .loginPage("/tologin");//实际上是通过/tologin的地址,转发到/templates/login.html
        //哪些资源可以直接访问
        http.authorizeRequests()
                .antMatchers("/login","/tologin","/back/css/**","/back/js/**","/back/fonts/**").permitAll()
                .anyRequest().authenticated();

        //暂时关闭csrf校验
        http.csrf().disable();
    }
}

(6)在浏览器中访问 127.0.0.1:8081/hello 时,会跳转到/tologin去,也就是自定义的login.html页面。输入用户账号和密码,登录成功后,会跳转到index.html。

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

五、Spring Security的授权

1、url 匹配规则详解


在SecurityConfig配置类中http.authorizeRequests()主要是对url 进行控制,也就是我们所说的授权(访问控制)。http.authorizeRequests()也支持连缀写法,总体格式为:

(1)url 匹配规则.权限控制方法
通过上面的格式可以有很多url 匹配规则和很多权限控制方法。这些内容进行各种组合就形成了Spring Security 中的授权。在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果,越是具体的应该放在前面,越是笼统的应该放到后面。

(2)anyRequest()
在之前认证过程中我们就已经使用过anyRequest(),表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证。例如:

anyRequest().authenticated();

(3)antMatchers()

方法定义如下:

public C antMatchers(String... antPatterns)

参数是可变参数,每个参数是一个ant 表达式,用于匹配URL规则。

规则如下:
? :匹配一个字符
* :匹配0 个或多个字符
** :匹配0 个或多个目录

在实际项目中经常需要放行所有静态资源,下面演示放行js文件夹下所有脚本文件。

.antMatchers("/js/**").permitAll()

还有一种配置方式是只要是.js 文件都放行

.antMatchers("/**/*.js").permitAll()

(4)regexMatchers()
使用正则表达式进行匹配。和antMatchers()主要的区别就是参数,antMatchers()参数是ant 表达式,regexMatchers()参数是正则表达式。
演示所有以.js 结尾的文件都被放行。

.regexMatchers(".+[.]js").permitAll()

无论是antMatchers()还是regexMatchers()都具有两个参数的方法,其中第一个参数都是HttpMethod,表示请求方式,当设置了HttpMethod 后表示只有设定的特定的请求方式才执行对应的权限设置。

(5)mvcMatchers()
mvcMatchers()适用于配置了servletPath 的情况。servletPath 就是所有的URL 的统一前缀。在SpringBoot 整合SpringMVC 的项目中可以在application.properties 中添加下面内容设置ServletPath。

spring.mvc.servlet.path= /etc

在Spring Security 的配置类中配置.servletPath()是mvcMatchers()返回值特有的方法,antMatchers()和regexMatchers()没有这个方法。在servletPath()中配置了servletPath 后,mvcMatchers()直接写SpringMVC 中@RequestMapping()中设置的路径即可。

.mvcMatchers("demo").servletPath("/etc").permitAll()

如果不习惯使用mvcMatchers()也可以使用antMatchers(),下面代码和上面代码是等效的。

.antMatchers("/etc/demo").permitAll()

2、权限控制方法详解


Spring Security 匹配了URL 后调用了permitAll()表示无需授权,随意访问。在Spring Security 中提供了多种内置控制。

(1)permitAll():表示所匹配的URL任何人都允许访问。

(2)uthenticated():表示所匹配的URL需要被认证才能访问。

(3)anonymous():表示可以匿名访问匹配的URL。和permitAll()效果类似,对于匿名访问的用户,Spring Security支持为其建立一个匿名AnonymousAuthenticationToken存放在SecurityContextHolder中,这就是匿名认证。这样在以后进行权限认证或者做其它操作时就不需要再判断SecurityContextHolder中持有的Authentication对象是否为null,直接把它当做一个正常的Authentication进行使用。

(4)denyAll():表示所匹配的URL 都不允许被访问。

(5)rememberMe():被“remember me”的用户允许访问。

(6)fullyAuthenticated():用户通过正常登录认证的而不是被remember me 的,才可以访问。

3、角色权限判断


(1)hasAuthority(String)
判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建User 对象时指定的。下图中admin 就是用户的权限。admin 严格区分大小写。在配置类中通过hasAuthority(“admin”)设置具有admin 权限时才能访问。

.antMatchers("/main.html").hasAuthority("admin")

(2)hasAnyAuthority(String …)

如果用户具备给定权限中某一个,就允许访问。下面代码中由于大小写和用户的权限不相同,所以用户无权访问/main.html

.antMatchers("/main.html").hasAnyAuthority("adMin","admiN")

(3)hasRole(String)
如果用户具备给定角色就允许访问。否则出现403。参数取值来源于自定义登录逻辑UserDetailsService 实现类中创建User 对象时给User 赋予的授权。在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。例如:ROLE_admin 其中admin 是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写admin 即可。否则启动报错。

(4)hasAnyRole(String …)
如果用户具备给定角色的任意一个,就允许被访问

(5)hasIpAddress(String)
如果请求是指定的IP 就允许被访问。可以通过request.getRemoteAddr()获取ip 地址。需要注意的是在本机进行测试时localhost 和127.0.0.1 输出的ip地址是不一样的。

六、Spring Security的授权使用

1、Spring Security的授权案例


(1)创建Controller类,超级管理员和普通管理员访问不同的url地址:

  • 拥有admin的角色的用户可以访问AdminController类
package com.etc.securitydemo.controller;

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

@Controller
@RequestMapping("admin")
public class AdminController {
    @GetMapping("add")
    @ResponseBody
    public String add(){
        return "admin add";
    }
    @GetMapping("list")
    @ResponseBody
    public String list(){
        return "admin list";
    }
}
  • 拥有manager的角色的用户可以访问CommonManagerController类
package com.etc.securitydemo.controller;

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

@Controller
@RequestMapping("manager")
public class CommonManagerController {
    @GetMapping("add")
    @ResponseBody
    public String add(){
        return "manager add";
    }
    @GetMapping("list")
    @ResponseBody
    public String list(){
        return "manager list";
    }
}

(2)修改SecurityConfig配置类。

package com.etc.securitydemo.config;

import com.etc.securitydemo.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
//WebSecurityConfigurerAdapter web安全配置适配器
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("userDetailServiceImpl")
    private UserDetailServiceImpl userDetailServiceImpl;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailServiceImpl);
    }
    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception{
        //指定表单验证(formlogin)有关的内容
        http.formLogin().loginProcessingUrl("/login")//处理登录的控制器的url地址
                .defaultSuccessUrl("/toindex",true)//登录成功后默认跳转url
                .loginPage("/tologin");//实际上是通过/tologin的地址,转发到/templates/login.html
        //哪些资源可以直接访问
        http.authorizeRequests()              .antMatchers("/login","/tologin","/back/css/**","/back/js/**","/back/fonts/**").permitAll()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/manager/**").hasRole("manager")
                .anyRequest().authenticated();
        //暂时关闭csrf校验
        http.csrf().disable();
    }
}

(3)通过MybatisX generator插件生成role表的Role类、RoleMapper类、RoleMapper的xml文件。

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

import java.io.Serializable;

/**
 * null
 * @TableName tbl_role
 */
public class Role implements Serializable {
    /**
     * 
     */
    private Integer id;

    /**
     * 
     */
    private String name;

    /**
     * 
     */
    private String remark;

    private static final long serialVersionUID = 1L;

    /**
     */
    public Integer getId() {
        return id;
    }

    /**
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     */
    public String getName() {
        return name;
    }

    /**
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     */
    public String getRemark() {
        return remark;
    }

    /**
     */
    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        Role other = (Role) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
            && (this.getRemark() == null ? other.getRemark() == null : this.getRemark().equals(other.getRemark()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
        result = prime * result + ((getRemark() == null) ? 0 : getRemark().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", name=").append(name);
        sb.append(", remark=").append(remark);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}
  • RoleMapper.java
package com.etc.securitydemo.mapper;

import com.etc.securitydemo.entity.Role;

import java.util.List;

/**
 * @Entity com.etc.securitydemo.entity.Role
 */
public interface RoleMapper {

    int deleteByPrimaryKey(Long id);

    int insert(Role record);

    int insertSelective(Role record);

    Role selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(Role record);

    int updateByPrimaryKey(Role record);

    List<Role> selectByUserId(Integer uid);
}
  • 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.etc.securitydemo.mapper.RoleMapper">

    <resultMap id="BaseResultMap" type="com.etc.securitydemo.entity.Role">
        <id property="id" column="id" jdbcType="INTEGER"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="remark" column="remark" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,name,remark
    </sql>

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

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from tbl_role
        where  id = #{id,jdbcType=INTEGER} 
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.Role"
            useGeneratedKeys="true">
        insert into tbl_role
        ( id,name,remark
        )
        values (#{id,jdbcType=INTEGER},#{name,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR}
        ))
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.Role"
            useGeneratedKeys="true">
        insert into tbl_role
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">id,</if>
            <if test="name != null">name,</if>
            <if test="remark != null">remark,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="id != null">id = #{id,jdbcType=INTEGER},</if>
            <if test="name != null">name = #{name,jdbcType=VARCHAR},</if>
            <if test="remark != null">remark = #{remark,jdbcType=VARCHAR},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.etc.securitydemo.entity.Role">
        update tbl_role
        <set>
            <if test="name != null">
                name = #{name,jdbcType=VARCHAR},
            </if>
            <if test="remark != null">
                remark = #{remark,jdbcType=VARCHAR},
            </if>
        </set>
        where id = #{id,jdbcType=INTEGER}
    </update>
    <update id="updateByPrimaryKey" parameterType="com.etc.securitydemo.entity.Role">
        update tbl_role
        set 
            name =  #{name,jdbcType=VARCHAR},
            remark =  #{remark,jdbcType=VARCHAR}
        where   id = #{id,jdbcType=INTEGER} 
    </update>
    <select id="selectByUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select tbl_role.id,tbl_role.name,tbl_role.remark from tbl_role inner join tbl_user_role on tbl_role.id = tbl_user_role.rid where tbl_user_role.uid = #{uid,jdbcType=INTEGER}
    </select>
</mapper>

(4)修改UserDetailServiceImpl类。

  • UserDetailServiceImpl.java
package com.etc.securitydemo.service;

import com.etc.securitydemo.entity.LoginUser;
import com.etc.securitydemo.entity.Role;
import com.etc.securitydemo.mapper.LoginUserMapper;
import com.etc.securitydemo.mapper.RoleMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;

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


@Service("userDetailServiceImpl")
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private LoginUserMapper loginUserMapper;
    @Autowired
    private RoleMapper roleMapper;

    Logger logger = LoggerFactory.getLogger(UserDetailServiceImpl.class);

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        LoginUser loginUser = loginUserMapper.selectByName(s);
        logger.info(loginUser.toString());
        if(loginUser == null){
            throw new UsernameNotFoundException("用户不存在!");
        }
        List<Role> roles = roleMapper.selectByUserId(loginUser.getUserid());
        List<GrantedAuthority> authorityList = new ArrayList<>();
        for(Role role : roles){
            authorityList.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
        }
        User user = new User(loginUser.getUsername(),loginUser.getUserpwd(),authorityList);
        return user;
    }
}

2、基于注解的访问控制

&emsp
在Spring Security 中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过在启动类上添加@EnableGlobalMethodSecurity 进行开启后使用,并根据功能的不同,在该注解中添加不同的属性。这些注解可以写到Service 接口或方法上上也可以写到Controller或Controller 的方法上。通常情况下都是写在控制器方法上的,控制接口URL 是否允许被访问。如果没有授权,则报500异常。

@Secured:在方法前或类前,是专门用于判断是否具有角色的,该注解的值为UserDetailServiceImpl类中设置的角色名称,要以ROLE_开头。

package com.etc.securitydemo.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Secured(value="ROLE_admin")
    @GetMapping("add")
    public String add(){
        return "product add";
    }

    @Secured(value="ROLE_manager")
    @GetMapping("list")
    public String list(){
        return "product list";
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值