源码地址
springboot2教程系列
mysql
mysql-connector-java
5.1.47
com.alibaba
druid
1.1.12
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-thymeleaf
spring:
messages:
basename: i18n/Messages,i18n/Pages
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 配置当前要使用的数据源的操作类型
driver-class-name: org.gjt.mm.mysql.Driver # 配置MySQL的驱动程序类
url: jdbc:mysql://47.106.106.53:3306/security?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8 # 数据库连接地址
username: root # 数据库用户名
password: Rojao@123
spring.jpa.database: mysql
spring.jpa.database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
# 显示后台处理的SQL语句
spring.jpa.show-sql: true
# 自动检查实体和数据库表是否一致,如果不一致则会进行更新数据库表
spring.jpa.hibernate.ddl-auto: create
spring.jpa.open-in-view: false
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的权限认证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 允许所有用户访问"/"和"/index.html"
http.authorizeRequests()
.antMatchers("/", "/index.html").permitAll() //定义不需要认证就可以访问
.antMatchers("/level1/**").hasRole("VIP1") //需要拥有VIP1权限
.anyRequest().authenticated() // 其他地址的访问均需验证权限
.and()
//开启cookie保存用户数据
.rememberMe()
//设置cookie有效期
.tokenValiditySeconds(60 * 60 * 24 * 7)
.and()
.formLogin() // 定义当需要用户登录时候,转到的登录页面
.loginPage("/login.html") // 登录页
.failureUrl("/login-error.html").permitAll()
.and()
.logout()
.logoutSuccessUrl("/index.html");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
/* auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("USER");*/
}
@Override
public void configure(WebSecurity web) throws Exception {
//静态资源忽略认证
web.ignoring().antMatchers("/css/**");
}
/**
* 配置登录验证
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
return new MyPasswordEncoder();
}
}
通过@EnableWebSecurity注解开启Spring Security的功能 继承WebSecurityConfigurerAdapter,并重写它的方法来设置一些web安全的细节 configure(HttpSecurity http)方法,通过authorizeRequests()定义哪些URL需要被保护、哪些不需要被保护。例如以上代码指定了/和/home不需要任何认证就可以访问,其他的路径都必须通过身份验证。 通过formLogin()定义当需要用户登录时候,转到的登录页面。 configureGlobal(AuthenticationManagerBuilder auth)方法,在内存中创建了一个用户,该用户的名称为admin,密码为123456,用户角色为USER。
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
//MD5Util.encode((String) charSequence);
System.out.println(charSequence.toString());
return null;
}
@Override
public boolean matches(CharSequence charSequence, String s) {
System.out.println(charSequence);
System.out.println(s);
return s.equals(charSequence.toString());;
}
}
指定了密码的加密方式(5.0 版本强制要求设置),因为我们数据库是明文存储的,所以明文返回即可
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private SysUserDao sysUserDao;
/**
* 授权的时候是对角色授权,认证的时候应该基于资源,而不是角色,因为资源是不变的,而用户的角色是会变的
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUserEntity userEntity = sysUserDao.findByUserName(username);
if (null == userEntity) {
throw new UsernameNotFoundException(username);
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
List<SysRoleEntity> roleList = userEntity.getRoleList();
if(roleList == null || roleList.size() == 0){
return new User(userEntity.getUserName(), userEntity.getPassword(), authorities);
}
for (SysRoleEntity role : roleList) {
List<SysPermissionEntity> permList = role.getPermissionEntityList();
if(permList == null){
continue;
}
for(SysPermissionEntity permission : permList){
//添加用户的权限
authorities.add(new SimpleGrantedAuthority(permission.getCode()));
}
}
return new User(userEntity.getUserName(), userEntity.getPassword(), authorities);
}
}
需要重写 loadUserByUsername 方法,参数是用户输入的用户名。返回值是UserDetails,这是一个接口,一般使用它的子类org.springframework.security.core.userdetails.User,它有三个参数,分别是用户名、密码和权限集。
hasPermission()
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object targetPermission) {
// 获得loadUserByUsername()方法的结果
User user = (User)authentication.getPrincipal();
// 获得loadUserByUsername()中注入的角色
Collection<GrantedAuthority> authorities = user.getAuthorities();
for(GrantedAuthority authority : authorities){
if(authority.getAuthority().equals(targetPermission)){
return true;
}
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
return false;
}
}
@RestController
public class PermissionController {
@RequestMapping("/perm")
@PreAuthorize("hasPermission('/perm','perm')")
public String perm(){
return "success";
}
}
@PreAuthorize("hasPermission('/perm','perm')")
是关键,参数1指明了访问该接口需要的url,参数2指明了访问该接口需要的权限。
public class MyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public MyAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
return null;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String userName = (String) req.getSession().getAttribute("username");
if("test".equals(userName)){
super.unsuccessfulAuthentication(req, res,new InsufficientAuthenticationException("输入的验证码不正确"));
}
// super.unsuccessfulAuthentication(req, res,new InsufficientAuthenticationException("输入的验证码不正确"));
chain.doFilter(request, response);
}
}
@Data
@Entity
@Table(name = "sys_user")
public class SysUserEntity {
@Id
@GeneratedValue
@Column(name = "user_id")
private Long userId;
@Column(nullable = false,unique = true, length = 50)
private String userName;
@Column(nullable = false, length = 200)
private String password;
@ManyToMany(cascade = {CascadeType.ALL},fetch = FetchType.EAGER)
@JoinTable(name="sys_user_role_map",joinColumns={@JoinColumn(name="user_id")},inverseJoinColumns={@JoinColumn(name="role_id")})
List<SysRoleEntity> roleList;
}
@Data
@Entity
@Table(name = "sys_role")
public class SysRoleEntity {
@Id
@Column(name = "role_id")
private Long roleId;
@Column(nullable = false, length = 200)
private String roleName;
@ManyToMany(cascade = {CascadeType.ALL},fetch = FetchType.EAGER)
@JoinTable(name="sys_role_permission_map",joinColumns={@JoinColumn(name="role_id")},inverseJoinColumns={@JoinColumn(name="perm_id")})
List<SysPermissionEntity> permissionEntityList;
}
@Data
@Entity
@Table(name = "sys_permission")
public class SysPermissionEntity {
@Id
@Column(name = "perm_id")
private Long permId;
@Column(length = 30)
private String code;
}
@Repository
public interface SysUserDao extends JpaRepository<SysUserEntity, Long> {
SysUserEntity findByUserName(String userName);
}