Spring Security 初体验

导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.1.2.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>cn.runjava.security</groupId>
   <artifactId>spring-security</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>spring-security</name>
   <description>Demo project for Spring Boot</description>

   <properties>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <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.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <optional>true</optional>
      </dependency>

      <dependency>
         <groupId>cn.hutool</groupId>
         <artifactId>hutool-all</artifactId>
         <version>4.1.19</version>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
       
   </dependencies>
    
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>

</project>

认证过程

package cn.runjava.security.springsecurity.system.security;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

/**
 * @author 郑查磊
 */
@Slf4j
@Component
public class UserDetailsServiceRunJava implements UserDetailsService {


    private final String ROLE = "ROLE_";

    /**
     * 密码加密
     */
    @Autowired
    private
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 从数据查询出查询出用户角色信息
        String password = username;

        // 从数据查询出加密后的密码
        String encodePassword = passwordEncoder.encode(password);

        // 账户是否启用 激活的概念 如果用户已启用,则设置为 true
        boolean enable = true;
        // 账户是否过期 如果帐户尚未过期,则设置为 true
        boolean accountNonExpired = true;
        // 密码是否过期 如果凭据未过期,则设置为 true
        boolean credentialsNonExpired = true;
        // 账户为被锁定 如果帐户未锁定,则设置为 true
        boolean accountNonLocked = true;
        // 权限集合 如果他们提供了正确的用户名和密码并且启用了用户,则应授予调用者权限。不是空的。
        ArrayList<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        // 默认添加一个角色 为admin
        grantedAuthorityList.add(new SimpleGrantedAuthority(ROLE + "ADMIN"));
        // 添加一个权限为 queryUser
        grantedAuthorityList.add(new SimpleGrantedAuthority("queryUser"));
        // 简单版本 默认为true  避免这些 概念性的设置
//        new User(username, encodePassword, grantedAuthorityList);  
        User user = new User(username, encodePassword, enable, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorityList);
        log.info("登录认证:" + JSONUtil.toJsonPrettyStr(user));
        return user;
    }

}

Spring security config

package cn.runjava.security.springsecurity.system.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author 郑查磊
 */
@Order(99)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class SecurityWebApplicationInitializerRunJava extends WebSecurityConfigurerAdapter {


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


    /**
     * 退出认证后do....
     */
    @Autowired
    private LogoutSuccessHandlerRunJava logoutSuccessHandler;

    /**
     * 认证成功后的处理
     */
    @Autowired
    private AuthenticationSuccessHandlerRunJava authenticationSuccessHandler;


    /**
     * 失败后的处理
     */
    @Autowired
    private AuthenticationFailureHandlerRunJava authenticationFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 禁用 csrf  -- 这里如果开启了 iframe 会出错 详细情况不清楚
                .csrf().disable()
                // 在这里的authorizeRequests 权限访问路径
                .authorizeRequests()
                // localhost:8080/admin
                // 例如 resources 下所有的请求 还有signup  about 的请求 都可以任意访问
                .antMatchers("/resources/**", "/signup ", "/about", "/system/logout", "/system/login.html").permitAll()
                // admin 下所有请求 需要admin 角色才能访问
                .antMatchers("/admin/**").hasRole("ADMIN")
                // db 请求 需要同时拥有 ADMIN 和 DB的角色
                .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
                // 剩下所有的 请求 都必须经过身份认证 也就是 login
                .anyRequest().authenticated()


                .and()
                // 基于表单认证
                .formLogin()
                // 认证地址
                .loginProcessingUrl("/login")
                //  认证页面 需要配置一下
                .loginPage("/system/login.html")
                // 认证时候的 用户名参数
                .usernameParameter("username")
                // 认证时候的 密码参数
                .passwordParameter("password")
                // 认证成功后的处理
                .successHandler(authenticationSuccessHandler)
                // 失败后的处理
                .failureHandler(authenticationFailureHandler)
                // 认证失败转跳的路径
//                .failureForwardUrl("/logerror.html")
                // 认证成功后转跳的页面
//                .successForwardUrl("/index.html")

                .and()
                // 设置退出认证URL
                // 触发注销的URL(默认值为/logout)如果启用了CSRF保护(默认),则请求也必须是POST。
                .logout().logoutUrl("/logout")
                // 退出认证成功 跳到认证页面 /login?logout
//                .logoutSuccessUrl("/login.html")
                // 这里设置退出认证后的一些操作
                .logoutSuccessHandler(logoutSuccessHandler)
                // 退出时候注销session  true
                .invalidateHttpSession(true)
                // 退出时删除的cookie信息
                .deleteCookies("user");
    }


}

## 退出登录Handler

package cn.runjava.security.springsecurity.system.security;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 郑查磊
 * @TODO 退出登录后要做的事情 do....
 */
@Slf4j
@Component
public class LogoutSuccessHandlerRunJava implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("退出登录!" + JSONUtil.toJsonPrettyStr(authentication));
        response.sendRedirect("/system/logout.html");
    }
}

登录成功Handler

package cn.runjava.security.springsecurity.system.security;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 郑查磊
 * @TODO 登录成功后做的事情
 */
@Component
public class AuthenticationSuccessHandlerRunJava implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        authentication.getDetails();
        if(authentication instanceof UsernamePasswordAuthenticationToken){
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) authentication;
            Object principal = usernamePasswordAuthenticationToken.getPrincipal();
            if(principal instanceof User){
                User user = (User) principal;
                // 设置到cookie 中 该用户的 登录账号
                response.addCookie(new Cookie("user",user.getUsername()));
                response.sendRedirect("/system/index.html");
            }
        }

    }

}
package cn.runjava.security.springsecurity.system.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 郑查磊
 * @TODO 失败后的处理
 */
@Slf4j
@Component
public class AuthenticationFailureHandlerRunJava implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

    }

}

编写自定义登录页面的controller

package cn.runjava.security.springsecurity.system.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * @author 郑查磊
 */
public class BaseController {

    /**
     * 拿到当前登陆的用户信息
     *
     * @param request
     * @return
     */
    public UserDetails getUserName(HttpServletRequest request) {
		return	UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
    .getAuthentication()
    .getPrincipal();
    }
}
package cn.runjava.security.springsecurity.system.controller;

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

import javax.servlet.http.HttpServletRequest;

/**
 * @author 郑查磊
 */
@Controller
@RequestMapping("/system")
public class SecurityController extends BaseController {


    /**
     * 自定义登录页面
     *
     * @return
     */
    @GetMapping("/login.html")
    public String loginView() {
        return "/login";
    }

    /**
     * 首页
     *
     * @param request
     * @param model
     * @return
     */
    @GetMapping("/index.html")
    public String indexView(HttpServletRequest request, Model model) {
        model.addAttribute("username", getUserName(request));
        return "/index";
    }

    /**
     * 退出前提示
     *
     * @param request
     * @param model
     * @return
     */
    @GetMapping("/logout.html")
    public String logout(HttpServletRequest request, Model model) {
        model.addAttribute("username", getUserName(request));
        return "/logout";
    }

    /**
     * 登录失败
     *
     * @param model
     * @return
     */
    @GetMapping("/logerror")
    public String logerror(Model model) {
        return "/logerror";
    }
}

测试controller

package cn.runjava.security.springsecurity.controller;

import cn.runjava.security.springsecurity.entity.User;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * @author 郑查磊
 */

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/getUser")
    @PreAuthorize("hasAuthority('user')")
    public User getUser(Integer id) {
        return new User(id, "小石头", "不告诉你!");
    }

    @GetMapping("/addUser")
    @PreAuthorize("hasRole('ADMIN')")
    public User addUser(Integer id) {
        return new User(id, "小石头", "不告诉你!");
    }

    @GetMapping("/queryUser")
    @PreAuthorize("hasAuthority('queryUser')")
    public User queryUser(Integer id) {
        return new User(id, "小石头", "不告诉你!");
    }

}

用到的entity

package cn.runjava.security.springsecurity.entity;

import lombok.Data;

/**
 * @author 郑查磊
 */
@Data
public class User {
    private Integer id;
    private String username;
    private String password;

    public User() {
    }

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }
}

自定义的登录页面 首页等...

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring Security</title>
</head>
<body>
    <h1 th:text="'登录成功! 尊敬的用户: ' + ${username}"></h1>
    <a th:href="@{/system/logout.html}">退出登录</a>
</body>
</html>

logerror.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录失败</title>
</head>
<body>
    <h1 th:text=""></h1>
    <a th:href="@{/login}"></a>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>自定义登录页面</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
    <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
    <div class="container">
        <form class="form-signin" method="post" th:attr="action=@{/login}">
            <h2 class="form-signin-heading">Please sign in</h2>
            <p>
                <label for="username" class="sr-only">Username</label>
                <input type="text" id="username" name="username" class="form-control" placeholder="Username" required
                       autofocus>
            </p>
            <p>
                <label for="password" class="sr-only">Password</label>
                <input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
            </p>
            <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
        </form>
    </div>
</body>
</html>

logout.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>退出登录成功!</title>
</head>
<body>
    <h1 th:text="'当前要退出的用户: ' + ${username}"></h1>
    <a th:href="@{/logout}">点击退出登录</a>
</body>
</html>

总结

cn.runjava.security.springsecurity.system.security.UserDetailsServiceRunJava

看懂此文章估计需要 基础 估计就是springmvc 和 spring 的一些 bean 概念 都不难...

这里写的多了一点 包括了 自定义登录页面和地址 都可以配置 详细看代码的注释即可

主要是这个类 在用户登录时候的认证信息 添加角色 还有密码 没有写读取数据库 可以自己添加上去

这里有一个概念的问题就是 ROLEAuthority 在spring security

但是我们做设计的时候可以做一些变通 这里下次写 还有403错误 可以添加拦截 哈哈 下次补上

其实配置不难 就是资源的权限配置以及 这个用户的认证过程2点

其他的就是你自己对于接口的权限配置了

注意的坑: .logoutSuccessUrl("/login.html").logoutSuccessHandler(logoutSuccessHandler) 只能存在一个 建议在Handler中 能做的更完善 可控

项目下载地址:链接: https://pan.baidu.com/s/1_8xmY0mki2C0f8vmSXPW0w 提取码: 77jq