Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring-security其实就是用filter,多请求的路径进行过滤。
如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去。
**如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以<a
href="https://jq.qq.com/?_wv=1027&k=52sgH1J"
target="_blank">
加入我们的java学习圈,点击即可加入
</a>
,共同学习,节约学习时间,减少很多在学习中遇到的难题。**
本文假设你已经引入spring-boot-starter-web。已经是个SpringBoot项目了,如果不会搭建,可以打开这篇文章看一看《SpringBoot入门建站全系列(一)项目建立》。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring-security一般需要从数据库中查询用户信息的,所以这里还是要把mybatis引入,以查询用户信息使用。
application.properties 中需要添加下面的配置:
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.dbcp2.max-wait-millis=60000
spring.datasource.dbcp2.min-idle=20
spring.datasource.dbcp2.initial-size=2
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.connection-properties=characterEncoding=utf8
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.test-while-idle=true
spring.datasource.dbcp2.test-on-borrow=true
spring.datasource.dbcp2.test-on-return=false
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boot?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=cff
spring.datasource.password=123456
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
这里的配置主要就是数据库连接和数据源、mybatis的配置。因为要连接数据库。
Spring Security的核心配置就是继承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置。
这个配置指明了用户名密码的处理方式、请求路径的开合、登录登出控制等和安全相关的配置。
ApiSecurityConfig:
package com.cff.springbootwork.security.config;
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 com.cff.springbootwork.security.handler.AjaxAuthFailHandler;
import com.cff.springbootwork.security.handler.AjaxAuthSuccessHandler;
import com.cff.springbootwork.security.handler.AjaxLogoutSuccessHandler;
import com.cff.springbootwork.security.handler.UnauthorizedEntryPoint;
import com.cff.springbootwork.security.provider.SimpleAuthenticationProvider;
@EnableWebSecurity
public class ApiSecurityConfig {
@Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private SimpleAuthenticationProvider simpleAuthenticationProvider;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(simpleAuthenticationProvider);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint())
.and().headers()
.frameOptions().disable().and().authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/login.html").permitAll()
.antMatchers("/error.html").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/pub/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("userName").passwordParameter("password")
.loginProcessingUrl("/login")
.successHandler(new AjaxAuthSuccessHandler())
.failureHandler(new AjaxAuthFailHandler())
.and()
.logout()
.logoutUrl("/logout").logoutSuccessHandler(new AjaxLogoutSuccessHandler());
}
}
}
这里:
SimpleAuthenticationProvider实现了AuthenticationProvider 接口的authenticate方法,提供用户名密码的校验,校验成功后,生成UsernamePasswordAuthenticationToken。
SimpleAuthenticationProvider:
package com.cff.springbootwork.security.provider;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.stereotype.Component;
import com.cff.springbootwork.mybatis.domain.UserInfo;
import com.cff.springbootwork.mybatis.service.UserInfoService;
@Component
public class SimpleAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserInfoService userInfoService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getPrincipal().toString();
UserInfo user = userInfoService.getUserInfoByUserName(userName);
if (user == null) {
throw new BadCredentialsException("查无此用户");
}
if (user.getPasswd() != null && user.getPasswd().equals(authentication.getCredentials())) {
Collection<? extends GrantedAuthority> authorities = AuthorityUtils.NO_AUTHORITIES;
return new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPasswd(), authorities);
} else {
throw new BadCredentialsException("用户名或密码错误。");
}
}
@Override
public boolean supports(Class<?> arg0) {
return true;
}
}
UnauthorizedEntryPoint:
package com.cff.springbootwork.security.handler;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import com.cff.springbootwork.security.model.ResultCode;
import com.cff.springbootwork.security.model.ResultModel;
import com.fasterxml.jackson.databind.ObjectMapper;
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ResultModel rm = new ResultModel(ResultCode.CODE_40004);
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
mapper.writeValue(response.getWriter(), rm);
}
}
AjaxAuthSuccessHandler:
package com.cff.springbootwork.security.handler;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import com.cff.springbootwork.security.model.ResultCode;
import com.cff.springbootwork.security.model.ResultModel;
import com.fasterxml.jackson.databind.ObjectMapper;
public class AjaxAuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
ResultModel rm = new ResultModel(ResultCode.CODE_00000);
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
mapper.writeValue(response.getWriter(), rm);
}
}
AjaxAuthFailHandler:
package com.cff.springbootwork.security.handler;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import com.cff.springbootwork.security.model.ResultCode;
import com.cff.springbootwork.security.model.ResultModel;
import com.fasterxml.jackson.databind.ObjectMapper;
public class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
ResultModel rm = new ResultModel(ResultCode.CODE_00014.getCode(), exception.getMessage());
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
mapper.writeValue(response.getWriter(), rm);
}
}
AjaxLogoutSuccessHandler:
package com.cff.springbootwork.security.handler;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import com.cff.springbootwork.security.model.ResultCode;
import com.cff.springbootwork.security.model.ResultModel;
import com.fasterxml.jackson.databind.ObjectMapper;
public class AjaxLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
ResultModel rm = new ResultModel(ResultCode.CODE_00000);
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
mapper.writeValue(response.getWriter(), rm);
}
}
UserInfoService:
package com.cff.springbootwork.mybatis.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cff.springbootwork.mybatis.dao.UserInfoDao;
import com.cff.springbootwork.mybatis.domain.UserInfo;
@Service
public class UserInfoService {
@Autowired
UserInfoDao userInfoDao;
public UserInfo getUserInfoByUserName(String userName){
return userInfoDao.findByUserName(userName);
}
}
UserInfoDao:
package com.cff.springbootwork.mybatis.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.cff.springbootwork.mybatis.domain.UserInfo;
@Mapper
public interface UserInfoDao {
@Select({
"<script>",
"SELECT ",
"user_name as userName,passwd,name,mobile,valid, user_type as userType",
"FROM user_info",
"WHERE user_name = #{userName,jdbcType=VARCHAR}",
"</script>"})
UserInfo findByUserName(@Param("userName") String userName);
}
UserInfo:
package com.cff.springbootwork.mybatis.domain;
public class UserInfo {
private String userName;
private String passwd;
private String name;
private String mobile;
private Integer valid;
private String userType;
public UserInfo() {
}
public UserInfo(UserInfo src) {
this.userName = src.userName;
this.passwd = src.passwd;
this.name = src.name;
this.mobile = src.mobile;
this.valid = src.valid;
}
public UserInfo(String userName, String passwd, String name, String mobile, Integer valid) {
super();
this.userName = userName;
this.passwd = passwd;
this.name = name;
this.mobile = mobile;
this.valid = valid;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getPasswd() {
return passwd;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getMobile() {
return mobile;
}
public void setValid(Integer valid) {
this.valid = valid;
}
public Integer getValid() {
return valid;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public String getIdentifyTable(){
return "user_info";
}
}
ResultCode:
package com.cff.springbootwork.security.model;
/**
* 响应码及其描述 Created by txl on 15/7/9.
*/
public enum ResultCode {
/**
* 通用
*/
CODE_00000("00000", "操作成功"),
CODE_00001("00001", "请求失败"),
CODE_00002("00002", "错误的请求方法"),
CODE_00003("00003", "非法的参数字段"),
CODE_00004("00004", "异常抛出"),
CODE_00005("00005", "权限不足"),
CODE_00006("00006", "分页limit参数错误"),
CODE_00007("00007", "分页offset参数错误"),
CODE_00009("00009", "请求过于频繁"),
CODE_00010("00010", "数据已存在"),
CODE_00011("00011", "数据不存在"),
CODE_00012("00012", "参数缺失"),
CODE_00013("00013", "系统维护中"),
CODE_00014("00014", "token缺失"),
CODE_00015("00015", "token失效"),
CODE_00016("00016", "签名错误"),
CODE_10000("10000", "操作部分成功"),
/**
* 系统
*/
CODE_30000("30000", "系统ID错误"),
/**
* 授权
*/
CODE_40001("40001", "用户未找到"),
CODE_40002("40002", "该用户状态异常"),
CODE_40003("40003", "该用户已被删除"),
CODE_40004("40004", "授权异常"),
CODE_99999("99999", "签名无效");
private String code;
private String desc;
ResultCode(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
/**
* 根据code匹配枚举
*
* @param code
* @return
*/
public static ResultCode getResultCodeByCode(String code) {
for (ResultCode resultCode : ResultCode.values()) {
if (code.equals(resultCode.getCode())) {
return resultCode;
}
}
return null;
}
public static ResultCode getResultCodeByDesc(String desc) {
for (ResultCode resultCode : ResultCode.values()) {
if (desc.equals(resultCode.getDesc())) {
return resultCode;
}
}
return null;
}
}
ResultModel:
package com.cff.springbootwork.security.model;
/**
*/
public class ResultModel {
private String errorCode;
private String message;
private Object data;
public ResultModel(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}
public ResultModel(String errorCode, String message, Object data) {
this.errorCode = errorCode;
this.message = message;
this.data = data;
}
public ResultModel(ResultCode resultCodeEnum, Object data) {
this.errorCode = resultCodeEnum.getCode();
this.message = resultCodeEnum.getDesc();
this.data = data;
}
public ResultModel(ResultCode resultCodeEnum) {
this.errorCode = resultCodeEnum.getCode();
this.message = resultCodeEnum.getDesc();
}
public String geterrorCode() {
return errorCode;
}
public void seterrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}