springSecurity设置用户名和密码
一.配置文件中设置
springl.security.user.name=xxx
spring.security.user.password=xxx
二.配置类中设置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//加密处理密码
String encode = new BCryptPasswordEncoder().encode("213");
auth.inMemoryAuthentication()
.withUser("lucy")
.password(new BCryptPasswordEncoder().encode("123"))
.roles("role");
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
三.自定义编写实现类
-
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
实现UserDetailService类重写loadUserByUsername(),在其中编写根据输入的用户名从数据库中获取密码的方法,并完善逻辑
@Service(value="userDetailsService")//value可以不写,@service自动按照类型注入 public class MyUserDetailService implements UserDetailsService { @Autowired SpringSecurityMapper springSecurityMapper; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { log.info("用户名是:{}",userName); LambdaQueryWrapper<SpringSecurity> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(!StringUtils.isEmpty(userName), SpringSecurity::getUserName, userName); SpringSecurity springSecurity = springSecurityMapper.selectOne(queryWrapper); if (springSecurity == null) { throw new UsernameNotFoundException("用户名不存在"); } List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE"); return new User(springSecurity.getUserName(),springSecurity.getPassword(), auths); } }
-
编写配置类继承WebSecurityConfigurerAdapter,并设置密码加密规则
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * @Description 设置密码加密规则,并对密码进行加密和匹配 * @return */ @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
springSecurity自定义认证功能
一.编写登陆功能的Controller
@Autowired
LoginService loginService;
@PostMapping("/user/login")
public Result login(@RequestBody UserLogin userLogin) throws Exception {
Result result = loginService.login(userLogin);
return result;
}
二.在Service类中编写登陆认证功能
-
编写Controller接口,调用Service中的认证方法
-
在service方法中需要调用authenticationManager类,需要新建配置类,标注@EnableSecurity注解,并将AuthenticationConfiguration注入到Service中;
-
调用AuthenticationConfiguration的getAuthenticationManager()方法获取authenticationManager对象,并调用authenticationManager的authenticate()方法进行验证,该方法需要一个UsernamePasswordAuthenticationToken对象,将接口中传入的用户的用户名、密码、权限等信息封装到该对象中并传入方法;
-
authenticationManager的authenticate()方法会调用自定义的实现了UserDetailService中的lodaUserByUserName()方法来进行用户验证
-
自定义MyUserDetailsService实现UserDetailsService接口,重写loadUserByUserName()方法,该方法会接收之前封装在UserNamePasswordAuthenticationToken对象中的用户名信息,然后再该方法中通过用户名信息去查询数据库中存储的用户信息
-
在loadUserByUserName()方法中查询到用户信息后,检查这个用户信息是否存在,如果不存在抛出异常(自定义逻辑),如果存在将查询到的用户信息封装到一个实现了UserDetails接口的类中,并将该类返回,该类需要重写UserDetails接口中的方法
-
方法经过其他逻辑后会再次返回LoginService实现类中的authenticationManager的authenticate()方法
-
authenticationManager的authenticate()方法会返回一个Authentication对象,如果该对象为空,则登陆失败,否则其中的principal属性即使我们的loadUserByUserName()方法返回的实现了UserDetails接口对象,对象中封装了我们从数据库中查询到的完整信息,我们将该Principal对象强转为我们实现了UserDetails接口的对象,就可以调用相关的方法获取该对象的属性信息
-
根据返回的对象的属性信息返回JWT信息
Service登陆认证方法代码
//LoginService-------------------------------------------------------------------------
public interface LoginService {
Result login(UserLogin userLogin) throws Exception;
}
//LoginServiceImpl----------------------------------------------------------------------
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
AuthenticationConfiguration authenticationConfiguration;
@Autowired
RedisTemplate redisTemplate;
@Override
public Result login(UserLogin userLogin) throws Exception {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLogin.getUserName(), userLogin.getPassword());
//AuthenticationManager authenticate 进行用户认证
AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没有通过,给出对应的提示
if (Objects.isNull(authenticate)){
throw new RuntimeException("登陆失败");
}
//如果认证ton过了,使用userid生成一个jwt
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUserLogin().getId().toString();
HashMap<String, Object> map = new HashMap<>();
map.put("userId", userId);
String jwt = JWTUtil.setHS256JWT(map, "test");
HashMap<String, Object> result = new HashMap<>();
result.put("token", jwt);
//把完整的用户信息存入Redis中,userid作为key
ValueOperations ops = redisTemplate.opsForValue();
ops.set("login"+userId,loginUser);
return Result.ok(result);
}
}
重写了UserDetailsService接口的方法的代码
@Service
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("用户名是:{}",username);
LambdaQueryWrapper<UserLogin> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq( UserLogin::getUserName, username);
UserLogin user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}else {
return new LoginUser(user);
}
}
自定义实现了UserDetails接口的pojo类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
//注入实体类
private UserLogin userLogin;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return userLogin.getPassword();
}
@Override
public String getUsername() {
return userLogin.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true ;
}
@Override
public boolean isEnabled() {
return true;
}
}
配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* @Description 设置密码加密规则,并对密码进行加密和匹配
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//关闭csrf
//禁止session功能
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
//对于登陆接口,允许匿名访问
.antMatchers("/user/login").anonymous()
.anyRequest().authenticated();//除了上面的路径,其他路径都需要认证
}
}
配置springSecurityJWT过滤器
一.自定义filter类
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
@Autowired
RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取Token
String token = request.getHeader("token");
//如果token为空,放行(放行后springSecurity后续会处理异常)
if (!StringUtils.hasText(token)) {
filterChain.doFilter(request,response);
return;
}
//解析Token获取其中的userId
Claims test = JWTUtil.parseJWT(token);
String userId = (String) test.get("userId");
//获取Redis中存入的用户信息
LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get("login" + userId);
if (Objects.isNull(loginUser)) {
throw new RuntimeException("用户为登陆");
}
//存入SecurityContextHolder
//TODO 获取权限信息封装到auhtenticationToken信息中
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request,response);
}
}
二.在配置类中注入并配置JWT过滤器
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/login").anonymous()
.anyRequest().authenticated();
//过滤器配置
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
权限系统
一.在security的配置类上添加注解
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//权限功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JWTFilter jwtFilter;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//关闭session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login").anonymous()//登陆之后不许再次访问
.anyRequest().authenticated();//其他请求都必须进行登陆认证
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
二.为接口资源添加所需权限
@GetMapping("/test")
@PreAuthorize("hasAuthority('system:dept:list')")
public String test(){
return "hello";
}
三.完善UserDetails接口的实现类,并查询后端返回权限集合
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
//权限列表
private List<String> permission;
//忽略序列化,redis为了安全不存储SimpleGrantedAuthority类,让其不序列化
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
public LoginUser(User user, List<String> permission) {
this.user = user;
this.permission = permission;
}
/**
* @Description 获取权限的方法,需要返回泛型为GrantedAuthority的实现类的值,这里为SimpleGrantedAuthority
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//如果不是第一次,authorities不为空直接返回
if (authorities != null) {
return authorities;
}
//如果是第一次,authorities为空,将通过构造器传入的权限列表转换为泛型为SimpleGrantedAuthority的权限列表
authorities = permission.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return authorities;
}
public String getNickName(){
return user.getNickName();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserService userService;
@Autowired
MenuMapper menuMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//从数据库中查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userService.getOne(queryWrapper);
if (ObjectUtil.isEmpty(user)){
throw new UsernameNotFoundException("用户昵称不存在");
}
List<String> list = menuMapper.selectPermsByUserId(user.getId());
return new LoginUser(user,list);
}
}
四.在过滤器中封装权限信息
@Component
public class JWTFilter extends OncePerRequestFilter {
@Autowired
UserService userService;
@Autowired
RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
//如果请求头中没有token信息,放行交给security后续过滤器处理异常信息
if (!StringUtils.hasText(token)) {
filterChain.doFilter(request, response);
return;
}
//如果有token信息将token信息中的用户信息存在SecurityContextHolder中
Claims claims = JWTUtil.parseJWT(token);
String userId = (String) claims.get("userId");
LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get("login:" + userId);
if (ObjectUtil.isEmpty(loginUser)) {
throw new RuntimeException("请重新登陆");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
//用户存在则将用户信息存入SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
自定义异常
工具类
package com.epidemicprevent.system.utils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Queue;
public class WebUtils {
public static String renderString(HttpServletResponse response, String string) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
自定义实现类
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
Result result = Result.forbiiden("权限不足");
String string = JSON.toJSONString(result);
WebUtils.renderString(response, string);
}
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
Result result = Result.unAuthorized("登陆失败");
String string = JSON.toJSONString(result);
WebUtils.renderString(response, string);
}
}
在SecurityConfig中添加配置
//配置处理器
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)//权限不足处理器
.authenticationEntryPoint(authenticationEntryPoint);//认证失败处理器
跨域请求
springboot开启跨域
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置循序跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
.allowedOrigins("*")
//是否允许cookie
.allowCredentials(true)
//设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
//设置允许的请求头
.allowedHeaders("*")
//跨域允许的时间
.maxAge(3600);
}
}
springSecurity开启跨域
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JWTFilter jwtFilter;
@Autowired
AccessDeniedHandler accessDeniedHandler;
@Autowired
AuthenticationEntryPoint authenticationEntryPoint;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//关闭session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login").anonymous()//登陆之后不许再次访问
.anyRequest().authenticated();//其他请求都必须进行登陆认证
//配置JWT过滤器
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
//配置处理器
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)//权限不足处理器
.authenticationEntryPoint(authenticationEntryPoint);//认证失败处理器
http.cors();//关闭跨域问题
}
}
自定义权限验证
一.自定义实现类比编写权限验证逻辑
@Component("ex")//指定容器名称
public class YFExpression {
public Boolean hasAuthority(String authority){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser principal = (LoginUser) authentication.getPrincipal();
List<String> permission = principal.getPermission();
return permission.contains(authority);
}
}
二.在@preAuhtorize注解中调用我们自己的方法
@GetMapping("/test")
@PreAuthorize("@ex.hasAuthority('system:dept:list')")
public String test(){
return "hello";
}
标签:return,String,spring,class,Override,new,security,public
From: https://www.cnblogs.com/yufou/p/16919205.html