前言
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"); }}
通过重写WebMvcConfigurer
的addViewControllers
方法,添加四个路由,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');
添加mysql
和mybatis 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
改为重写WebSecurityConfigurerAdapter
的configure(AuthenticationManagerBuilder auth)
方法,security将数据库查询的用户密码和界面传入的密码进行比较,一致则登录成功并跳转,否则停留在登录界面并返回错误信息。
结果:
源码地址: GITHUB