1.引入pom依赖
<!--这里可以不指定Spring Security的版本号,它会根据SpringBoot的版本来匹配对应的版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 其他工具依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.3</version>
</dependency>
2.实现自定义配置,首先要创建一个继承于WebSecurityConfigurerAdapter的配置类。
(1)实现自定义拦截配置,首先得告诉Spring Security,用户信息从哪里获取,以及用户对应的角色等信息。这里就需要重写WebSecurityConfigurerAdapter的configure (AuthenticationManagerBuilder auth)方法了。
(2)这个方法将指使Spring Security去找到用户列表,然后再与想要通过拦截器的用户进行比对。
import com.example.springboot.filter.CustomAuthenticationFilter;
import com.example.springboot.handler.FailureHandler;
import com.example.springboot.handler.OutSuccessHandler;
import com.example.springboot.handler.PermissionHandler;
import com.example.springboot.handler.SuccessHandler;
import com.example.springboot.service.impl.UserLoginServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* security config.
*
* @author lxp
* @date 2020 -10-19 17:11:48
*/
@Configuration
//@EnableWebSecurity注解是Spring Security用于启用web安全的注解
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SuccessHandler successHandler;
@Autowired
private FailureHandler failureHandler;
@Autowired
private OutSuccessHandler outSuccessHandler;
@Autowired
private PermissionHandler permissionHandler;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* 注册自定义过滤器
* @return
* @throws Exception the exception
* @author lxp
* @date 2020 -10-23 10:39:28
*/
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationFailureHandler(failureHandler);
//过滤器拦截的url要和登录的url一致,否则不生效
filter.setFilterProcessesUrl("/login");
//重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不加要自己组装AuthenticationManager
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
/**
* 密码加密方式
*/
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 自定义UserDetailsService,从数据库中读取用户信息
* @return
*/
@Bean
public UserLoginServiceImpl userLoginService(){
return new UserLoginServiceImpl();
}
/**
* 关联用户认证
* @param auth
* @throws Exception the exception
* @author lxp
* @date 2020 -10-20 10:45:59
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//告诉Spring Security你的UserDetailsService实现类是哪个就可以了,它会去调用loadUserByUsername()来查找用户
auth
.userDetailsService(userLoginService())
.passwordEncoder(passwordEncoder());
}
//Spring Security的请求拦截配置方法是用户存储配置方法的重载方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 所有用户均可访问的资源(开放的)
.antMatchers( "/favicon.ico","/css/**","/common/**","/js/**","/images/**","/druid/**","/login","/logout").permitAll()
// hasAnyRole鉴权(对接口进行User或Admin鉴权)
.antMatchers( "/test/**","/user/**").hasAnyRole("USER","ADMIN")
// hasRole对admin下的接口 需要ADMIN权限
.antMatchers("/admin/**").hasRole("ADMIN")
// 任何尚未匹配的URL只需要验证用户即可访问
.anyRequest().authenticated()
//对于没有配置权限的其他请求允许匿名访问
//.and().anonymous()
// 配置登录功能,失败跳转error
.and()
//自定义权限(用于json登录)
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginProcessingUrl("/login")
// 登陆成功后的处理动作
.successHandler(successHandler)
// 登陆失败后的处理动作
.failureHandler(failureHandler)
// 配置退出登录页面
.and().logout()
.logoutUrl("/logout")
.logoutSuccessHandler(outSuccessHandler)
// 权限拒绝的页面
.and().exceptionHandling().accessDeniedHandler(permissionHandler)
.and().cors()
// 禁用 csrf,防止跨站(必须要禁用csrf才能使用ajax登录方式)
.and().csrf().disable();
}
}
Spring Security的用户存储配置有多个方案:
(1)内存用户存储
(2)数据库用户存储
(3)LDAP用户存储
(4)自定义用户存储
下面只介绍自定义用户存储:
自定义用户存储,就是自行使用认证名称来查找对应的用户数据,然后交给Spring Security使用。我们需要定义一个实现UserDetailsService的service类:
import com.example.springboot.entity.SysUser;
import com.example.springboot.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* User login service.
*
* @author lxp
* @date 2020 -10-20 10:43:53
*/
@Service
public class UserLoginServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws AuthenticationException {
SysUser query = new SysUser();
query.setUsername(username);
SysUser user = sysUserMapper.selectOne(query);
//权限集合
Collection<GrantedAuthority> collection = new ArrayList<>();
if (user == null) {
throw new BadCredentialsException("用户" + username +"不存在!");
}
// hasRole方法拼接ROLE_
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+user.getUsername().toUpperCase());
grantedAuthorities.add(grantedAuthority);
// 把获得的账号密码传给security的User
//注意自定义配置类对密码什么加密方式,这里就调用什么加密方式。
return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),grantedAuthorities);
}
}
说明:
该类只需要实现一个方法:loadUserByUsername()。该方法需要做的是使用传过来的username来匹配一个带有密码等信息的用户实体。需要注意的是这里的User类需要实现UserDetails,也就是说,查到的信息里,必须得有Spring Security所需要的信息。
4.自定义配置类SpringSecurityConfig 请求拦截说明:
5.如果需要json登录需要配置过滤器:
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* 自定义过滤器
*
* @author lxp
* @date 2020 -10-23 10:17:58
*/
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// if (request.getContentType() == null) {
// throw new AuthenticationServiceException("未登录");
// }
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
authRequest = new UsernamePasswordAuthenticationToken(
authenticationBean.get("username"), authenticationBean.get("password"));
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
"", "");
} finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
else {
return super.attemptAuthentication(request, response);
}
}
}
6.自定义配置类SpringSecurityConfig中登录成功、失败、权限、退出登录处理:
(1)登录成功处理类:
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* The type Success handler.
*
* @author lxp
* @date 2020 -10-22 14:59:38
*/
@Slf4j
@Component
public class SuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// authentication 登录成功的对象,默认用户名
log.info("登录成功,{}", authentication);
// 跨域处理
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
// 允许的请求方法
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
// 允许的请求头
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 设置响应头
httpServletResponse.setContentType("application/json;charset=utf-8");
authentication = SecurityContextHolder.getContext().getAuthentication();
// 返回值
// 拼装数据返回对象
Dict res = Dict.create().set("code", 0).set("msg", "登录成功").set("data", authentication);
// 设置一下输出json字符串时的编码
String contentType = ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8);
// 响应json字符串
ServletUtil.write(httpServletResponse, JSONUtil.toJsonStr(res), contentType);
}
}
(2)登录失败处理类:
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
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;
/**
* The type Failure handler.
*
* @author lxp
* @date 2020 -10-22 14:59:41
*/
@Slf4j
@Component
public class FailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception
) throws IOException, ServletException {
log.info("登录失败,{}", exception);
Dict res = Dict.create().set("code", 1000).set("msg", "登录失败");
// 根据返回的异常类,确定登录失败的原因
if (exception instanceof UsernameNotFoundException) {
res.set("data", "用户名不存在");
} else if (exception instanceof LockedException) {
res.set("data", "账号被锁定");
} else if (exception instanceof DisabledException) {
res.set("data", "账号被禁用");
} else if (exception instanceof CredentialsExpiredException) {
res.set("data", "密码过期");
} else if (exception instanceof AccountExpiredException) {
res.set("data", "账号过期");
} else if (exception instanceof BadCredentialsException) {
res.set("data", "账号密码输入有误");
} else {
res.set("data", "登录失败(" + exception.getMessage() + ")");
}
// 响应json字符串
String contentType = ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8);
ServletUtil.write(response, JSONUtil.toJsonStr(res), contentType);
}
}
(3)权限不足处理类:
import com.alibaba.fastjson.JSON;
import com.example.springboot.common.Response;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by Administrator on 2020/10/23.
*/
@Component
public class PermissionHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
// 设置响应头
httpServletResponse.setContentType("application/json;charset=utf-8");
// 返回值
Response result = Response.createBySuccessWithoutData("权限不足,无法登陆");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
(4)退出登录处理类:
import com.alibaba.fastjson.JSON;
import com.example.springboot.common.Response;
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;
/**
* The type Out success handler.
*
* @author lxp
* @date 2020 -10-22 14:59:33
*/
@Component
public class OutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// 设置响应头
httpServletResponse.setContentType("application/json;charset=utf-8");
// 返回值
Response result = Response.createBySuccessWithoutData("退出登陆成功");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
7.实体类SysUser:
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@Data
@Table(name = "sys_user")
public class SysUser implements UserDetails,Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
private String username;
private String password;
private List<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
实体类User:
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* The type User.
*
* @author lxp
* @date 2020 -10-19 15:10:03
*/
@Data
@Table(name = "user")
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements Serializable {
private static final long serialVersionUID = 1222221L;
@Id
@Column(name = "id")
private Integer id;
@Column(name = "user_name")
private String userName;
@Column(name = "pass_word")
private String passWord;
@Column(name = "real_name")
private String realName;
}
8.其他类:接口、实现类、mapper在上一篇博客。
9.编写测试类:
import com.example.springboot.common.Response;
import com.example.springboot.entity.User;
import com.example.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试类模块
*
* @author lxp
* @date 2020 -10-19 14:50:51
*/
@RestController
public class TestController {
@Value("${server.port}")
String port;
@Autowired
private UserService userService;
@GetMapping(value = "/test")
public String test(@RequestParam(required = false) Integer id){
User user = userService.getUserInfo(id);
StringBuilder stringBuilder = new StringBuilder("欢迎进入"+port+"号客户端!"+"
"+"人员名称是:"+user.getUserName());
return stringBuilder.toString();
}
@PostMapping(value = "/user/testUser")
public String testUser(){
StringBuilder stringBuilder = new StringBuilder("欢迎==员工==登录!");
return stringBuilder.toString();
}
@PostMapping(value = "/admin/testAdmin")
public String testAdmin(){
StringBuilder stringBuilder = new StringBuilder("欢迎==管理员==登录!");
return stringBuilder.toString();
}
}