本教程值值详细讲解spring security的实现,没有任何杂质,只是用于记录。不公开!!
SpringBoot项目的创建就不在说了,直接跳过
一、引入相关依赖
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
二、写了几个工具类
- 定义后台统一返回代码
/**
******************************************************************
* 后台统一返回代码
******************************************************************
* 作者:
* 时间:
*/
public enum ResultEnum {
SUCCESS(200, "请求成功"),
FAILURE(100, "请求失败"),
USER_NO_ACCESS(300, "用户无权访问"),
USER_NEED_AUTHORITIES(102, "用户未登录"),
USER_LOGIN_FAILED(101,"用户账号或密码错误"),
USER_LOGIN_SUCCESS(201, "用户登录成功"),
USER_LOGOUT_SUCCESS(202, "用户登出成功"),
TOKEN_IS_BLACKLIST(301, "token失效"),
LOGIN_IS_OVERDUE(207, "登录已失效");
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
/**
* 根据枚举code,返回枚举项
* @param code 枚举代码
* @return 返回值:枚举项
*/
public static ResultEnum parse(int code){
ResultEnum[] values = values();
for (ResultEnum value : values) {
if(value.getCode() == code){
return value;
}
}
throw new RuntimeException("未知代码");
}
}
- 定义后台同意返回数据模板
/**
******************************************************************
* 定义后台返回对象模板
******************************************************************
* 作者:
* 时间:
*/
public class AjaxDone {
private Integer code; // 请求状态码
private String msg; // 请求提时信息
private Object data; // 请求数据
// 省略getter和getter方法
}
三、实现Spring Security各个核心接口,处理不同状态
- 实现AuthenticationEntryPoint接口,处理用户未登录的状况
/**
******************************************************************
* 用户未登陆时,访问接口时返回的数据
******************************************************************
* 作者:
* 时间:
*/
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;charset:utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_NEED_AUTHORITIES)));
}
}
- 实现AccessDeniedHandler接口,处理无权访问的情况
/**
******************************************************************
* 用户无权访问接口时返回的数据
******************************************************************
* 作者:
* 时间:
*/
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;charset:utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_NO_ACCESS)));
}
}
- 实现AuthenticationFailureHandler接口,处理用户登录失败
/**
******************************************************************
* 用户访问登录接口,登陆失败的时候,返回的 数据
******************************************************************
* 作者:
* 时间:
*/
@Component
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;charset:utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_LOGIN_FAILED)));
}
}
- 实现AuthenticationSuccessHandler接口,处理登录成功的情况
/**
******************************************************************
* 用户访问登录接口,登陆成功的时候,返回的 数据
******************************************************************
* 作者:
* 时间:
*/
@Component
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
SelfUserDetails userDetails = (SelfUserDetails) authentication.getPrincipal();
// 生成token,以后完成
String jwtToken = "token";
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;charset:utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_LOGIN_SUCCESS, jwtToken)));
}
}
- 实现LogoutSuccessHandler接口,处理退出成功
/**
******************************************************************
* 用户访问登出接口,登出成功的时候,返回的数据
******************************************************************
* 作者:
* 时间:
*/
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;charset:utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_LOGOUT_SUCCESS)));
}
}
- 实现UserDetails实现自定义对象
/**
******************************************************************
* 实现系统的自定义对象
******************************************************************
* 作者:
* 时间:
*/
public class SelfUserDetails implements UserDetails {
private Integer id;
private String username;
private String password;
private Set<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; }
public void setAuthorities(Set<? extends GrantedAuthority> authorities) { this.authorities = authorities; }
// 最重点Ⅰ
@Override
public String getPassword() { return this.password; }
// 最重点Ⅱ
@Override
public String getUsername() { return this.username; }
public void setUsername(String username) { this.username = username; }
public void setPassword(String password) { this.password = password; }
//账号是否过期
@Override
public boolean isAccountNonExpired() { return true; }
//账号是否锁定
@Override
public boolean isAccountNonLocked() { return true; }
//账号凭证是否未过期
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
}
四、权限访问控制
- 实现自定义公式验证验证权限
/**
******************************************************************
* 自定义公式,权限验证
******************************************************************
* 作者:
* 时间:
*/
@Component("rbacauthorityservice")
public class RbacAuthorityService {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object userInfo = authentication.getPrincipal();
boolean hasPermission = false;
if (userInfo instanceof UserDetails) {
String username = ((UserDetails) userInfo).getUsername();
// 这可以在redis中获取
// 获取资源
Set<String> urls = new HashSet();
// 这些 url 都是要登录后才能访问,且其他的 url 都不能访问!
urls.add("/demo/**");//application.yml里设置了项目路径,百度一下我就不贴了
AntPathMatcher antPathMatcher = new AntPathMatcher();
for (String url : urls) {
if (antPathMatcher.match(url, request.getRequestURI())) {
hasPermission = true;
break;
}
}
return hasPermission;
} else {
return false;
}
}
}
- 实现自定义的jwt拦截器
/**
******************************************************************
* 每次请求,都会经过这个过滤器,将用户解析出来放到上下文中
* 模拟已经登陆的状态
******************************************************************
* 作者:
* 时间:
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
SelfUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring("Bearer ".length());
// String username = JwtTokenUtil.parseToken(authToken, "_secret");
// 这里先不解析,写死为admin
String username = "admin";
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
filterChain.doFilter(request, response);
}
}
五、Spring Security核心配置
- 继承UserDetailsService,用户认证的业务代码
/**
******************************************************************
* 用户认证的业务逻辑
******************************************************************
* 作者:
* 时间:
*/
@Component
public class SelfUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(SelfUserDetailsService.class);
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//通过username查询用户
// 这里先固定死用户, 真正的业务需要从数据库获取
SelfUserDetails user = userService.findUser(username);
if(user == null){
//仍需要细化处理
throw new UsernameNotFoundException("该用户不存在");
}
Set authoritiesSet = new HashSet();
// 模拟从数据库中获取用户角色
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
authoritiesSet.add(authority);
user.setAuthorities(authoritiesSet);
logger.info("用户{}验证通过",username);
return user;
}
}
- 核心处理类
/**
******************************************************************
* Spring Security核心配置类
******************************************************************
* 作者:
* 时间:
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
@Autowired
AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陆时返回 JSON 格式的数据给前端(否则为 html)
@Autowired
AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登录成功返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxAuthenticationFailureHandler authenticationFailureHandler; //登录失败返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxLogoutSuccessHandler logoutSuccessHandler;//注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
@Autowired
AjaxAccessDeniedHandler accessDeniedHandler;//无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
@Autowired
SelfUserDetailsService userDetailsService; // 自定义user
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义的安全认证
// auth.authenticationProvider(provider);
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 去掉 CSRF
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,关闭token
.and()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()//定义哪些URL需要被保护、哪些不需要被保护
// 定义禁止拦截的资源
.antMatchers("/test/yu").permitAll()
// 定义指定的拦截
.anyRequest()//任何请求,登录后可以访问
.access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 动态 url 认证
.and()
.formLogin() //开启登录, 定义当需要用户登录时候,转到的登录页面
// .loginPage("/test/login.html")
// .loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler) // 登录成功
.failureHandler(authenticationFailureHandler) // 登录失败
.permitAll()
.and()
.logout()//默认注销行为为logout
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll();
// 记住我
http.rememberMe().rememberMeParameter("remember-me")
.userDetailsService(userDetailsService).tokenValiditySeconds(1000);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
}
}
这里密码使用BCryptPasswordEncoder加密,官方也推荐这个,将密码保存到数据库,记得用这个类加密一下哦。
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password = encoder.encode("000000"); // 将字符串‘000000’加密
boolean b = encoder.matches("000000", password); // 对比密码
}
来源:https://blog.csdn.net/zzxzzxhao/article/details/83381876
参考:
https://blog.csdn.net/zimou5581/article/details/89511381
https://blog.csdn.net/sinat_29899265/article/details/80771330
https://www.jianshu.com/p/ca4cebefd1cc