最近帮一个客户定制二手房屋交易系统,客户同学要求使用 Spring Security 作为安全框架。
我是在把项目完整开发完后,再改造整入 Spring Security 的。
用的也很简单,just do it
一、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
二、User 类实现 UserDetails
添加权限集合和重写获取用户名,密码,权限的方法
package com.example.ssm.rental.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.ssm.rental.common.base.BaseEntity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
/**
* 用户信息
*
* @author example
* @date 2022/4/27 12:08 下午
*/
@TableName("t_user")
@Data
public class User extends BaseEntity implements UserDetails {
/**
* 登录名
*/
private String userName;
/**
* 姓名称
*/
private String userDisplayName;
/**
* 密码
*/
private String userPass;
/**
* 身份证号码
*/
private String idCard;
/**
* 头像
*/
private String userAvatar;
/**
* 说明
*/
private String userDesc;
/**
* 0 正常
* 1 禁用
*/
private Integer status = 0;
/**
* 电子邮箱
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 职业
*/
private String job;
/**
* 业余爱好
*/
private String hobby;
/**
* 性别
*/
private String sex;
/**
* 角色:管理员admin/普通用户 user
*/
private String role;
/**
* 身份证照片
*/
private String idCardImg;
/**
* 未读消息
*/
@TableField(exist = false)
private Integer notReadMessageCount;
/**
* 未读消息
*/
@TableField(exist = false)
private String roleName;
@TableField(exist = false)
private List<GrantedAuthority> authorityList;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorityList;
}
@Override
public String getPassword() {
return this.userPass;
}
@Override
public String getUsername() {
return this.userName;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return status == 0;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
三、新建 UserDetailServiceImpl.java
重写根据用户名获取用户信息的方法
package com.example.ssm.rental.common.security;
import com.example.ssm.rental.entity.User;
import com.example.ssm.rental.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 java.util.Arrays;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询数据库
User user = userService.findByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + user.getRole());
user.setAuthorityList(Arrays.asList(grantedAuthority));
return user;
}
}
四、新建 MyAccessDeniedHandler
自定义 403 页面
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;
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/403");
}
}
五、创建 LoginValidateAuthenticationProvider
重写身份验证的方法
package com.example.ssm.rental.common.security;
import com.example.ssm.rental.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class LoginValidateAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取输入用户名
String username = authentication.getName();
//获取输入明文密码
String rawPassword =(String) authentication.getCredentials();
//根据用户名查询匹配用户
User user = (User)userDetailsService.loadUserByUsername(username);
//判断用户账户状态
if (!user.isEnabled()) {
throw new DisabledException("该账户已被禁用,请联系管理员");
} else if (!user.isAccountNonLocked()) {
throw new LockedException("该账号已被锁定");
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("该账号已过期,请联系管理员");
} else if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录");
}
//验证密码
if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
throw new BadCredentialsException("输入密码错误!");
}
System.out.println("认证成功!");
return new UsernamePasswordAuthenticationToken(user, rawPassword, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
//确保authentication能转成该类
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
六、创建 SecurityConfig
创建 Spring Security 核心配置类
package com.example.ssm.rental.common.security;
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.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//自定义认证
@Autowired
private LoginValidateAuthenticationProvider loginValidateAuthenticationProvider;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Bean
public PasswordEncoder passwordEncoder() {
// 密码不加密
return NoOpPasswordEncoder.getInstance();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置退出操作退出
http.logout()
.logoutSuccessUrl("/login").permitAll();
//配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/login");
//登录成功之后,跳转路径
http.headers().frameOptions().disable();
http.authorizeRequests()
.antMatchers("/assets/**", "/favicon.ico", "/house", "/area", "/house/**").permitAll()
.antMatchers("/admin/user", "/admin/user/**").hasRole("admin")
.antMatchers("/admin/news", "/admin/news/**").hasRole("admin")
.and()
.formLogin().loginPage("/login")//登录页面
.loginProcessingUrl("/login")//提交表单时处理登录判断操作
.defaultSuccessUrl("/admin")
.permitAll();
;//其余任何请求都需要通过验证
// 用户没有权限的页面处理
http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler) //配置403访问错误处理器
.and();
//关闭csrf跨域攻击防御
http.csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置认证方式为自定义认证
auth.authenticationProvider(loginValidateAuthenticationProvider);
}
}
七、登录手动调用 Spring Security 的 验证 方法
这里我们不用spring security 的 默认 /login 的提交,我们自己手动去调用
登录接口代码
/**
* 登录提交
*
* @param userName
* @return
*/
@RequestMapping(value = "/login/submit", method = RequestMethod.POST)
@ResponseBody
public JsonResult loginSubmit(@RequestParam("userName") String userName,
@RequestParam("userPass") String userPass,
HttpSession session) {
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(userName, userPass);
try {
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); // 这个非常重要,否则验证后将无法登陆
User user = userService.findByUserName(userName);
user.setNotReadMessageCount(messageService.countNotReadMessageSize(user.getId()));
session.setAttribute(Constant.SESSION_USER_KEY, user);
} catch (LockedException e) {
return JsonResult.error("账号被禁用,无法登录");
} catch (AuthenticationException e) {
return JsonResult.error("用户名或密码错误");
}
// 登录成功
return JsonResult.success("登录成功");
}
您可以选择一种方式赞助本站
支付宝扫一扫赞助
微信钱包扫描赞助
赏