前言
spring security 可以对网站进行用户访问控制(验证|authentication)和用户授权(authorization)。两者也在springboot 手册中明说到: authentication (who are you?) and authorization (what are you allowed to do?)。用户授权结合OAuth进行api或者第三方接入控制授权(授权),本文使用security进行用户登录,验证用户合法性(验证)。

参考:
1、官网,网站安全控制;
2、博客,用户登录验证a;用户登录验证b

1、创建不受访问限制的项目

1.1、初始化项目

在Spring Initializr 或者编辑器中生成空白项目,maven依赖添加web和thymeleaf就可以了,如下图:

1.2、创建两个展示界面

src/main/resources/templates/home.html<!doctype html><html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"><head>    <meta charset="UTF-8" />    <title>home</title></head><body>    <h1>Home Page</h1>    <p>click <a th:href="@{/hello}">here</a> to see a greeting.</p></body></html>

希望通过上面这个界面跳转到 /hello,hello界面内容如下:

src/main/resources/templates/hello.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">    <head>        <title>Hello World!</title>    </head>    <body>        <h1>Hello world!</h1>    </body></html>

页面创建完成后,配置路由控制界面跳转,通过配置文件方式添加路由:

package com.noel.handbook.accesscontroll.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class MvcConfig implements WebMvcConfigurer {    @Override    public void addViewControllers(ViewControllerRegistry registry) {        // TODO Auto-generated method stub        registry.addViewController("/home").setViewName("/home");        registry.addViewController("/").setViewName("/home");        registry.addViewController("/login").setViewName("/login");        registry.addViewController("/hello").setViewName("/hello");    }}

通过重写WebMvcConfigureraddViewControllers方法,添加四个路由,login界面在下面创建。
到现在,可以运行项目,浏览器输入地址ip:端口/ 或者 ip:端口/home查看界面输出,界面之间和url地址之间可以随意跳转。

2、添加security

我们想控制用户需要登录后才显示hello界面,并输出登录者的用户名。

pom添加如下依赖:

pom.xml<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-security</artifactId></dependency>

然后我们限制重启项目,可以看见控制台会输出一个密码。访问任意界面会显示一个springboot的默认登录界面:

2.1、 设置访问控制

package com.noel.handbook.accesscontroll.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;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.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.provisioning.InMemoryUserDetailsManager;@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Bean    @Override    protected UserDetailsService userDetailsService() {        // TODO Auto-generated method stub        UserDetails user = User.withDefaultPasswordEncoder().username("noel").password("123").roles("USER").build();        return new InMemoryUserDetailsManager(user);    }    @Override    protected void configure(HttpSecurity http) throws Exception {        // TODO Auto-generated method stub        http        .authorizeRequests()        .antMatchers("/", "/home").permitAll()        .anyRequest().authenticated()        .and()        .formLogin()        .loginPage("/login").permitAll()        .and()        .logout().permitAll();    }}

添加了一个继承WebSecurityConfigurerAdapter 的配置类,重写两个方法添加一些配置,并添加注解@EnableWebSecurity,开启Spring Security。
userDetailsService()这里实现了一个存在于内存中的用户,用户名是noel,密码是123,该用户的角色是USER,该角色spring 有自己的一套方法,一般以ROLE_开头的字符串说明。之后进行用户信息在数据库中的验证实现。 By the way, 不要忘了方法上的@Bean注解,不添加会无法成功登录。
configure(HttpSecurity http)定义了路径//home不需要访问控制,其它路径需要认证之后才能访问。登录成功后会跳转到用户之前想要访问的页面,loginPage("/login")自定义了一个用户登录界面,就不会是刚才的默认登录界面。

2.2、自定义登录界面

src/main/resources/templates/login.html<!doctype html><html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"><head>    <meta charset="UTF-8" />    <title>login</title></head><body>    <div th:if="${param.error}">无效用户名和密码</div>    <div th:if="${param.logout}">已登出</div>    <form th:action="@{/login}" method="post">        <div><label for="username">User Name: <input type="text" name="username" /></label></div>        <div><label for="password">password: <input type="password" name="password" /></label></div>        <div><input type="submit" value="Sign In"/></div>    </form></body></html>

用户通过访问上面页面,在表单输入用户姓名noel、密码123后提交到/login进行登录验证,登录成功后表示验证成,否则跳转到/login?logout,返回错误信息。

2.3、 用户等出

更改hello界面,显示登录成功后的用户姓名和登出按钮。

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">    <head>        <title>Hello World!</title>    </head>    <body>        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>        <form th:action="@{/logout}" method="post">            <input type="submit" value="Sign Out"/>        </form>    </body></html>

/logout登出成功会跳转到/login?logout

3、验证mysql数据库存放的用户

3.1、相关配置

因为平时我们可能比较频繁与写数据库操作逻辑,所以,下面的主要以代码为主,逻辑应该比较清晰。
用户表:三个字段,其中type为用户权限等级,0为管理员,1为普通用户,在用户权限检查(CustomUserDetailsService)中会用到。添加了一个admin用户,密码是123456 的BCrypt加密结果

DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (  `Id` int(11) NOT NULL AUTO_INCREMENT,  `userName` varchar(255) DEFAULT NULL COMMENT '姓名',  `password` varchar(255) DEFAULT NULL COMMENT '密码',  `type` INTEGER DEFAULT 0,  PRIMARY KEY (`Id`)) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户表';INSERT INTO `user` VALUES ('0', 'Admin', '$2a$10$7HMxhcXQXp5vtVm.fmWyK.NUYNrB1.6/FUfq3PiFnOnenCp/CVIDa', '1');

添加mysqlmybatis maven 依赖:

pom.xml<dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <scope>runtime</scope></dependency><dependency>    <groupId>org.mybatis.spring.boot</groupId>    <artifactId>mybatis-spring-boot-starter</artifactId>    <version>2.1.0</version></dependency>

yml配置:设置数据库访问和mapper位置。

spring:  datasource:    url: jdbc:mysql://ip:3306/数据库名?useUnicode=true&charset=UTF-8&useAffectedRows=true&useSSL=false    username: 数据库登录用户名    password: 数据库登录密码    driver-class-name: com.mysql.jdbc.Driver    mybatis:  mapper-locations: classpath:mapper/*.xml  type-aliases-package: com.noel.handbook.accesscontroll.model

数据库语句mapper/userMapper.xml: 根据用户名查询一个用户。

<?xml version = "1.0" encoding = "UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD com.example.Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd "><mapper namespace="com.noel.hadbook.accesscontroll.dao.IUser">    <select id="getUser" resultType="com.noel.hadbook.accesscontroll.model.UserModel">        SELECT * FROM user         <where>            name like CONCAT('%','${name}','%')        </where>    </select></mapper> 

3.2、 获取用户

获取用户:

package com.noel.handbook.accesscontroll.dao;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import com.noel.handbook.accesscontroll.model.UserModel;@Mapperpublic interface IUser {    /**     * 根据用户名获取一个用户     * @return 用户信息     * @author noel     * @date 2019年9月7日     */    UserModel getUser(@Param("name")String name);}
package com.noel.handbook.accesscontroll.model;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@AllArgsConstructor@NoArgsConstructorpublic class UserModel {    private String name;    private String pwd;    private Integer type;}

3.3、 接入Security并验证用户

/*** <p>Title: UserDetailsService.java</p>* <p>Description: 系统用户信息校验,权限检查</p>* <p>Copyright: Copyright (c) 2019</p>* <p>Company: cbpm</p>* @author noel* @date 2019年9月6日 */package com.noel.handbook.accesscontroll;import java.util.ArrayList;import java.util.List;import javax.annotation.Resource;import org.slf4j.Logger;import org.slf4j.LoggerFactory;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 com.noel.handbook.accesscontroll.dao.IUser;import com.noel.handbook.accesscontroll.model.UserModel;/** * 用户权限检查 * @author noel * @date 2019年9月6日 */@Servicepublic class CustomUserDetailsService implements UserDetailsService{        private static final Logger log = LoggerFactory.getLogger(CustomUserDetailsService.class);    @Resource    private IUser iUser;        /* (non-Javadoc)     * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)     */    @Override    public UserDetails loadUserByUsername(String arg0) throws UsernameNotFoundException {        // TODO Auto-generated method stub        UserModel userModel = iUser.getUser(arg0);        log.info("验证机制里面的用户",userModel);        if(null == userModel) {            throw new UsernameNotFoundException("用户不存在");        }        List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();        if(userModel.getType()==0) {            authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));        }else {            //其它所有用户都认为是普通用户            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));        }        return new User(userModel.getName(), userModel.getPwd(), authorities);    }}

以上思路是:根据用户名从数据库获取到用户信息,以及用户权限。如果登录失败,则返回错误信息并停留在登录界面;如果登录成功,则将用户名、密码、权限封装到SimpleGrantedAuthority

package com.noel.handbook.accesscontroll.config;import javax.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;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.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import com.noel.handbook.accesscontroll.CustomUserDetailsService;@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Resource    private CustomUserDetailsService userDetailsService;    //    @Bean//    @Override//    protected UserDetailsService userDetailsService() {//        // TODO Auto-generated method stub//        UserDetails user = User.withDefaultPasswordEncoder().username("noel").password("123").roles("USER").build();//        return new InMemoryUserDetailsManager(user);//    }    @Override    protected void configure(HttpSecurity http) throws Exception {        // TODO Auto-generated method stub        //...    }    @Override    @Autowired    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        // TODO Auto-generated method stub        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());    }    }

用户验证方法由UserDetailsService改为重写WebSecurityConfigurerAdapterconfigure(AuthenticationManagerBuilder auth)方法,security将数据库查询的用户密码和界面传入的密码进行比较,一致则登录成功并跳转,否则停留在登录界面并返回错误信息。

结果:

源码地址: GITHUB