欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring boot 整合Spring Security

程序员文章站 2022-04-19 22:33:48
...

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

实体类

package com.springsecutity.secutity.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class SysRole {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package com.springsecutity.secutity.entity;

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

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @Author: shanggq
 * @date: 2018/12/12
 * UserDetails 将当前用户交给 spring Security 去管理
 */

@Entity
public class SysUser implements UserDetails {


    @Id
    @GeneratedValue
    private Long id;

    private String username;
    private String password;
    // CascadeType.PERSIST 用于级联保存设置
//    FetchType.EAGER 在级联保存的时候需要首先获取数据
    @ManyToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.EAGER)
    private List<SysRole> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> auths = new ArrayList<>();
        List<SysRole> roles = this.getRoles();
        for (SysRole role : roles) {
            auths.add(new SimpleGrantedAuthority(role.getName()));
        }
        return auths;
    }


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

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

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

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

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

    public void setUsername(String username) {
        this.username = username;
    }

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

    public void setPassword(String password) {
        this.password = password;
    }

    public List<SysRole> getRoles() {
        return roles;
    }

    public void setRoles(List<SysRole> roles) {
        this.roles = roles;
    }
}

Msg用于传递数据,在页面进行数据访问显示

package com.springsecutity.secutity.entity;

/**
 * 用于传递用户的信息
 */
public class Msg {
    private String title;
    private String content;
    private String etraInfo;

    public Msg(String title, String content, String etraInfo) {
        this.title = title;
        this.content = content;
        this.etraInfo = etraInfo;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getEtraInfo() {
        return etraInfo;
    }

    public void setEtraInfo(String etraInfo) {
        this.etraInfo = etraInfo;
    }
}

MySecutityConfig 配置 核心配置

package com.springsecutity.secutity.config;

import com.springsecutity.secutity.service.CustomUserService;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

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

    @Bean
    public UserDetailsService customUserService() {
        return new CustomUserService();
    }

    /**
     * 用于设置用户登录成功和失败的页面,设置cookie的有效时间,设置私密
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.formLogin() // 通过formLogin 方法定制登录操作
//                .loginPage("/login") //定制登录页面的访问地址
//                .defaultSuccessUrl(".index") // 指定登录成功之后转向的页面
//                .failureUrl("/login?error") // 指定登录失败之后转向的页面
//                .permitAll()
//                .and()
//                .rememberMe() // 用于开启cookie存储用户的信息
//                .tokenValiditySeconds(60 * 60 * 24) // 设置cookie存储用户的时间
//                .key("myKey") // 设置cookie中的私密
//                .and()
//                .logout() // 开启注销的行为
//                .logoutUrl("/custom-logout") // 注销的时候的url路径
//                .logoutSuccessUrl("/logout-success") // 注销成功之后跳转的页面
//                .permitAll();
        http.authorizeRequests()
                .antMatchers("/css/**", "/js/**", "/images/**", "/webjars/**", "**/favicon.ico", "/index")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .rememberMe()
                .tokenValiditySeconds(60 * 60)
                .key("mykey")
                .rememberMeParameter("remember-me")
                .rememberMeCookieName("workspace")
                .and()
                .formLogin()
                .loginPage("/login")
//                .successHandler(new ForwardAuthenticationSuccessHandler("/"))
                .defaultSuccessUrl("/")
//                会去访问/login/error 的请求路径  ,如果是默认的/login?error 的路径 会默认有param 的属性
                .failureUrl("/login?error")
                .permitAll()
                .and()
                .logout()
                .permitAll();
//        用于关闭跨域
        http.csrf().disable();
    }

    /**
     * 用于授权
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService()).passwordEncoder(new MyPasswordEncoder());
    }

}

@EnableGlobalMethodSecurity(prePostEnabled = true) 该配置用于下面的密码的配置,需要使用到该配置 

设置webMvc的配置 : 该配置主要是用于设置当访问/login的进行统一的处理,该配置也可以设置在上面的配置中

package com.springsecutity.secutity.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }
}

密码的配置:加该配置的原因是,在其余操作完成之后,进行用户的登录的时候,会发现后台出现密码为空的错误

package com.springsecutity.secutity.config;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

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

Controller层

package com.springsecutity.secutity.controller;

import com.springsecutity.secutity.entity.Msg;
import com.springsecutity.secutity.entity.error;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;

@Controller
public class HomeController {
    @RequestMapping("/")
    public String index(Model model) {
        Msg msg = new Msg("测试标题", "测试内容", "额外信息,只对管理员展示,注销请求");
        model.addAttribute("msg", msg);
        return "index";
    }

    @RequestMapping("/login/error")
    public String error(Model model) {
        error error = new error("登录失败,请您重新登录", "");
        model.addAttribute("param", error);
        return "login";
    }

    @PreAuthorize("hasRole('ROLE_USER')" )
    @RequestMapping("/getName")
    @ResponseBody
    public String getName() {
        System.out.println(".....");
        return "asd";
    }
}

service层

package com.springsecutity.secutity.service;

import com.springsecutity.secutity.dao.SysUserRepository;
import com.springsecutity.secutity.entity.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class CustomUserService implements UserDetailsService {

    @Autowired
    private SysUserRepository sysUserRepository;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        SysUser username = sysUserRepository.findByUsername(s);
        if (username == null) {
            throw new UsernameNotFoundException("用户查询失败 。。");
        }
        return username;
    }
}

dao层,使用的hibernate进行数据的访问,如果使用mybatis,可直接将该接口修改为mybatis的接口

package com.springsecutity.secutity.dao;

import com.springsecutity.secutity.entity.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;

public interface SysUserRepository extends JpaRepository<SysUser, Long> {
    SysUser findByUsername(String username);
}

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <style type="text/css">
        body {
            padding-top: 50px;
        }

        .starter-tempalte {
            padding: 40px 15px;
            text-align: center;
        }
    </style>
</head>
<body>
<nav class="navber navbar-inverse navbar-fixed-top ">
    <div class="container">
        <a class="navbar-brand" href="#">Spring Security演示</a>
    </div>
    <div id="navbar" class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
            <!--<li><a th:href="@{/}">首页</a></li>-->
        </ul>
    </div>
</nav>
<div class="starter-tempalte">
    <p th:if="${param.logout}" class="bg-waring">已成功注销</p>
    <p th:if="${param.error}"  class="bg-waring">有错误,请重试</p>
    <h2>使用账号密码登录</h2>
    <form name="form" th:action="@{/login}" method="post">
        <div class="form-group">
            <label for="username">账号</label>
            <input type="text" class="form-control" name="username" id="username" value="" placeholder="账号">
        </div>
        <div class="form-group">
            <label for="password"></label>
            <input type="password" class="form-control" name="password" id="password" placeholder="密码">
        </div>
        <div>
            <!-- 是否记住我功能勾选框 -->
            <input id="remember-me" name="remember-me" type="checkbox"/>                
            <label for="remember-me">一周内记住我</label>
        </div>
        <input type="submit" id="login" value="Login" class="btn btn-primary">
    </form>

</div>
</body>
</html>

其中param这个参数,是Security底层里面的数据,在核心配置的时候,如果登录错误会跳转到login?error中,这个是请求的Security底层的方法,如果不想使用该功能,可将登录失败的地址修改成自己的请求地址即可

index页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<!--xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" 表示的是spring security 的标签支持-->
<head>
    <meta charset="UTF-8">
    <!--sec:authentication="name" 获得当前用户名-->
    <title sec:authentication="name"></title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <script type="text/javascript" th:src="@{js/jquery-1.8.3.js}"></script>
    <style type="text/css">
        body {
            padding-top: 50px;
        }

        .starter-tempalte {
            padding: 40px 15px;
            text-align: center;
        }
    </style>
</head>
<body>
<nav class="navber navbar-inverse navbar-fixed-top ">
    <div class="container">
        <a class="navbar-brand" href="#">Spring Security演示</a>
    </div>
    <div id="navbar" class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
            <li><a th:href="@{/}">首页</a></li>
        </ul>
        <ul class="nav navbar-nav">
            <li><a id="getname">用户名称</a></li>
        </ul>
    </div>
</nav>
<div class="container">
    <div class="starter-tempalte">
        <h1 th:text="${msg.title}"></h1>
        <p class="bg-primary" th:text="${msg.content}"></p>
        <div sec:authorize="hasRole('ROLE_ADMIN')">
            <p class="bg-info" th:text="${msg.etraInfo}"></p>
        </div>
        <div sec:authorize="hasRole('ROLE_USER')">
            <p class="bg-info" id="userRole">无更多信息</p>
        </div>
        <form th:action="@{/logout}" method="post">
            <input type="submit" class="btn btn-primary" value="注销">
        </form>
    </div>
</div>

</body>
<script type="text/javascript">
    $("#getname").on("click", function () {
        $.get("http://localhost:5000/getName",function (data) {
            if(data == null && data == ""){
                $("#userRole").text("无更多信息");
            }else{
                $("#userRole").text(data);
            }
        });
    });
</script>

</html>

该页面中测试了权限设置,在controller中添加了getName的方法,并在上面添加了权限限制,但是对于没有权限的用户的提示方法我没有思路,有解决办法的大佬希望能给我指点一下

sql

INSERT INTO SYS_USER (ID, username, PASSWORD) VALUES (1, 'wyf', 'wyf');
INSERT INTO SYS_USER (ID, username, PASSWORD) VALUES (2, 'wisely', 'wisely');
INSERT INTO SYS_ROLE (ID, name) VALUES (1, 'ROLE_ADMIN');
INSERT INTO SYS_ROLE (ID, name) VALUES (2, 'ROLE_USER');
INSERT INTO SYS_USER_ROLES (SYS_USER_ID, ROLES_ID) VALUES (1, 1);
INSERT INTO SYS_USER_ROLES (SYS_USER_ID, ROLES_ID) VALUES (2, 2);

pom.xml 使用到的依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <!-- thymeleaf 支持Security -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
    username: root
    password: root
  thymeleaf:
    cache: false
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
logging:
  level:
    org:
     springframework:
      security: INFO
server:
  port: 5000

本案例借鉴 《javaEE开发颠覆者》