在 springboot 项目中 , 引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
启动时
启动时 , 控制台可以看到 生成一个UUID 作为 密码
Using generated security password: 876205ea-25bd-47b2-9c68-e2ac52377915
用户名为 user
在 application.properties 配置文件 中加入
# 设置 用户名密码
spring.security.user.name=admin
spring.security.user.password=123
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* security 配置文件
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码生成器
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
// 无加密密码
return NoOpPasswordEncoder.getInstance();
}
/**
* 设置 用户 密码 及 角色
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("yuan@123")
.password("123")
.roles("admin");
}
}
/**
* 配置忽略掉的 URL 地址,一般对于静态文件
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**","/img/**","/font/**");
}
/**
* 请求属性配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html") //转向登录页面
.loginProcessingUrl("/doLogin") // 登录请求
.usernameParameter("username") // 账号标识
.passwordParameter("password") // 密码标识
//.successForwardUrl("/success") // 登录成功跳转(内部转, 登录成功跳转到指定请求)
.defaultSuccessUrl("/success") // 登录成功跳转(重定向, 登录成功就回到之前访问的资源)
.failureForwardUrl("/login.html")
.failureUrl("/login.html")
.permitAll()
.and()
.logout()
//.logoutUrl("/logout") // GET方式 调用logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST")) // POST 方式调用 logout
.logoutSuccessUrl("/login.html") // 退出转向
.invalidateHttpSession(true) // 清空session
.clearAuthentication(true) // 清空认证信息
.permitAll()
.and()
.csrf().disable();
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String sayHello(){
return "hello";
}
@RequestMapping("/success")
public String success(){
return "success";
}
}
//.successForwardUrl("/success")
//.defaultSuccessUrl("/success")
.successHandler((request,response, authentication)->{
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
out.flush();
out.close();
})
{
"password": null,
"username": "yuan@123",
"authorities": [
{
"authority": "ROLE_admin"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
}
//.failureForwardUrl("/login.html")
//.failureUrl("/login.html")
.failureHandler((request,response, exception)->{
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
String msg = "";
if (exception instanceof LockedException) {
msg = "账户被锁定,请联系管理员!";
} else if (exception instanceof CredentialsExpiredException) {
msg = "密码过期,请联系管理员!";
} else if (exception instanceof AccountExpiredException) {
msg = "账户过期,请联系管理员!";
} else if (exception instanceof DisabledException) {
msg = "账户被禁用,请联系管理员!";
} else if (exception instanceof BadCredentialsException) {
msg = "用户名或者密码输入错误,请重新输入!";
}
out.write(new ObjectMapper().writeValueAsString(msg));
out.flush();
out.close();
})
.exceptionHandling()
.authenticationEntryPoint((req, resp, authException) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("尚未登录,请先登录");
out.flush();
out.close();
}
)
.logout()
.logoutUrl("/logout")
//.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"))
//.logoutSuccessUrl("/login.html")
//.invalidateHttpSession(true)
//.clearAuthentication(true)
.logoutSuccessHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("注销成功");
out.flush();
out.close();
})
将原来方法注释, 使用新的方法
///**
// * 设置 用户 密码 及 角色
// * @param auth
// * @throws Exception
// */
//@Override
//protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication()
// .withUser("yuan")
// .password("123")
// .roles("admin");
//}
/**
* pring Security 支持多种数据源,例如内存、数据库、LDAP 等,
* 这些不同来源的数据被共同封装成了一个 UserDetailService 接口,
* 任何实现了该接口的对象都可以作为认证数据源。
* @return
*/
@Override
@Bean
protected UserDetailsService userDetailsService() {
// 在内存中存储, 创建两个账号 , 分别赋 admin 和 user 权限
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password("123").roles("admin").build());
manager.createUser(User.withUsername("yuan").password("123").roles("user").build());
return manager;
}
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
/**
* 只要登录就只可以访问
* @return
*/
@RequestMapping("/hello")
public String sayHello(){
return "hello";
}
/**
* 只有 admin 角色才能访问
* @return
*/
@GetMapping("/admin/hello")
public String admin() {
return "admin";
}
/**
* admin, user 角色都可以访问
* @return
*/
@GetMapping("/user/hello")
public String user() {
return "user";
}
}
/**
* 请求属性配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
.and()
...
将使用 admin 访问 /user/hello 时会报错
{
"timestamp": "2021-11-05T14:27:39.537+00:00",
"status": 403,
"error": "Forbidden",
"message": "",
"path": "/user/hello"
}
/**
* 角色继承
* @return
*/
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_admin > ROLE_user");
return hierarchy;
}
这样 使用 admin 访问 /user/hello 就可以了
实现 UserDetails 接口 , 覆盖对应的方法
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.Data;
import org.apache.ibatis.mapping.FetchType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
*
* @TableName sys_user
*/
@TableName(value ="sys_user")
@Data
public class UserEntity implements UserDetails, Serializable {
/**
*
*/
@TableId(type = IdType.AUTO)
private Integer userId;
/**
*
*/
private String userName;
/**
*
*/
private String userPass;
/**
*
*/
private String salt;
/**
*
*/
private String nickName;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
// 账户是否没有过期
@TableField(exist = false)
private boolean accountNonExpired = true;
//账户是否没有被锁定
@TableField(exist = false)
private boolean accountNonLocked = true;
//密码是否没有过期
@TableField(exist = false)
private boolean credentialsNonExpired = true;
//账户是否可用
@TableField(exist = false)
private boolean enabled = true;
@TableField(exist = false)
private List<RoleEntity> roles;
// 返回用户的角色信息
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (RoleEntity role : getRoles()) {
// 注意这里的 角色 前缀
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleCode()));
}
return authorities;
}
@Override
public String getPassword() {
return this.userPass;
}
@Override
public String getUsername() {
return this.userName;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.List;
/**
*
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity>
implements UserService, UserDetailsService {
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
UserEntity user = this.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, name));
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
user.setRoles(this.getRolesByUserId(user.getUserId()));
System.out.println("user = " + user);
return user;
}
public List<RoleEntity> getRolesByUserId(Integer userId){
// 通过 数据库 连表 , 根据 用户id 查询对应的 role 集合
return this.baseMapper.selectRoleListByUserId(userId);
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.*;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.io.PrintWriter;
/**
* security 配置类
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
/**
* 密码生成器
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
// 自带加密器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
/**
* 角色继承
* @return
*/
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_admin > ROLE_user");
return hierarchy;
}
/**
* 配置忽略掉的 URL 地址,一般对于静态文件
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**","/img/**","/font/**");
}
/**
* 请求属性配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/doLogin")
.successHandler((request,response, authentication)->{
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
out.flush();
out.close();
})
.failureHandler((request,response, exception)->{
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
String msg = "";
if (exception instanceof LockedException) {
msg = "账户被锁定,请联系管理员!";
} else if (exception instanceof CredentialsExpiredException) {
msg = "密码过期,请联系管理员!";
} else if (exception instanceof AccountExpiredException) {
msg = "账户过期,请联系管理员!";
} else if (exception instanceof DisabledException) {
msg = "账户被禁用,请联系管理员!";
} else if (exception instanceof BadCredentialsException) {
msg = "用户名或者密码输入错误,请重新输入!";
}
out.write(new ObjectMapper().writeValueAsString(msg));
out.flush();
out.close();
})
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("注销成功");
out.flush();
out.close();
})
.and()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint((req, resp, authException) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("尚未登录,请先登录");
out.flush();
out.close();
}
);
}
}