首先我们创建一个WebSecurityConfig类,继承自WebSecurityConfigurerAdapter, 然后编写基本类
package com.kason.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
/**
* Created by IBM on 2018/6/30.
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Http权限控制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/login").permitAll() //admin的登陆页面允许所有用户登入
.antMatchers("/user/login").permitAll() //用户的登陆页面允许所有人登入
.antMatchers("/static/**").permitAll() //静态资源允许全部获取权限
.antMatchers("/admin/**").hasRole("ADMIN") //除了/admin/login之外的/admin/**只允许admin权限用户使用
.antMatchers("/usr/**").hasAnyRole("ADMIN","USER")
.antMatchers("/api/user/**").hasAnyRole("ADMIN","USER")
.and()
.formLogin()
.loginProcessingUrl("/login") //配置角色登陆处理入口
.and();
}
}
这样当我们一开始输入http://localhost:8081/admin/center的时候就会跳转到http://localhost:8081/login登陆页面(此时是默认的SpringBoot的登陆页面),结果如图
自定义权限认证策略:
1 先使用内存配置进行测试
/**
* 自定义认证策略
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
// 内存配置, 测试使用
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN").and();
}
这样我们使用admin,admin来登陆http://localhost:8081/login 就可以登陆进行了。上面就是在内存中创建了一个ADMIN角色, 它的用户名是admin, 密码也是admin
2 使用数据库来完成角色认证
为了使用数据库实现自定义的权限认证,增加两张数据库表, user表和role表。 数据库数据信息如下:
user表
role表
针对这两张表编写对应的entity
package com.kason.entity;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Entity
// 映射到数据库小写的表
@Table(name = "user")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String password;
private String email;
@Column(name = "phone_number")
private String phoneNumber;
private int status;
@Column(name = "create_time")
private Date createTime;
@Column(name = "last_login_time")
private Date lastLoginTime;
@Column(name = "last_update_time")
private Date lastUpdateTime;
private String avatar;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Transient //非mysql字段, 不用校验
private List<GrantedAuthority> authorityList;
public List<GrantedAuthority> getAuthorityList() {
return authorityList;
}
public void setAuthorityList(List<GrantedAuthority> authorityList) {
this.authorityList = authorityList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorityList;
}
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return name;
}
@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 void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
public Date getLastUpdateTime() {
return lastUpdateTime;
}
public void setLastUpdateTime(Date lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
}
User类中有一个字段authorityList,这个是数据库表所没有的, 为了解决数据库检验的问题,使用@Transient申明不用校验。
@Transient //非mysql字段, 不用校验
private List<GrantedAuthority> authorityList;
package com.kason.entity;
import javax.persistence.*;
/**
* Created by IBM on 2018/6/30.
*/
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id")
private Long userId;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
实体类之后就是repository和service
repository 类: UserRepository, RoleRepository
package com.kason.repository;
import com.kason.entity.User;
import org.springframework.data.repository.CrudRepository;
/**
* Created by IBM on 2018/6/18.
*/
public interface UserRepository extends CrudRepository<User,Long> {
User findUserByName(String userName);
}
package com.kason.repository;
import com.kason.entity.Role;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
/**
* Created by IBM on 2018/6/30.
*/
public interface RoleRepository extends CrudRepository<Role, Long> {
List<Role> findRolesByUserId(long id);
}
service类: UserServiceImpl
package com.kason.service;
import com.kason.entity.User;
/**
* 用户服务
* Created by IBM on 2018/6/30.
*/
public interface IUserService {
User findUserByName(String userName);
}
package com.kason.service.user;
import com.kason.entity.Role;
import com.kason.entity.User;
import com.kason.repository.RoleRepository;
import com.kason.repository.UserRepository;
import com.kason.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* Created by IBM on 2018/6/30.
*/
@Service
public class UserServiceImpl implements IUserService {
@Autowired
UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
@Override
public User findUserByName(String userName) {
User user = userRepository.findUserByName(userName);
if (null == user) {
return null;
}
List<Role> rolesByUserId = roleRepository.findRolesByUserId(user.getId());
if (null == rolesByUserId) {
throw new DisabledException("no role");
}
List<GrantedAuthority> authorities = new ArrayList<>();
rolesByUserId.forEach(role -> authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())));
user.setAuthorityList(authorities);
return user;
}
}
新建一个security 的package包:编写AuthProvider类
image.png
package com.kason.security;
import com.kason.entity.User;
import com.kason.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* 自定义权限认真实现
* Created by IBM on 2018/6/30.
*/
public class AuthProvider implements AuthenticationProvider{
@Autowired
private IUserService iUserService;
// 因为密码是md5加密过的, 所以此处需要一个md5类解密
private final Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();
String inputPassword = (String)authentication.getCredentials();
// 从数据库中读取User
User user = iUserService.findUserByName(userName);
if (null == user) {
throw new AuthenticationCredentialsNotFoundException("auth error");
}
if (this.passwordEncoder.isPasswordValid(user.getPassword(),inputPassword, user.getId())) {
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
} else {
throw new BadCredentialsException("authError password not");
}
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
之后WebSecurityConfig类由原先的内存权限配置改成数据库权限配置
package com.kason.config;
import com.kason.security.AuthProvider;
import org.springframework.beans.factory.annotation.Autowired;
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.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
/**
* Created by IBM on 2018/6/30.
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Http权限控制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/login").permitAll() //admin的登陆页面允许所有用户登入
.antMatchers("/user/login").permitAll() //用户的登陆页面允许所有人登入
.antMatchers("/static/**").permitAll() //静态资源允许全部获取权限
.antMatchers("/admin/**").hasRole("ADMIN") //除了/admin/login之外的/admin/**只允许admin权限用户使用
.antMatchers("/usr/**").hasAnyRole("ADMIN","USER")
.antMatchers("/api/user/**").hasAnyRole("ADMIN","USER")
.and()
.formLogin()
.loginProcessingUrl("/login") //配置角色登陆处理入口
.and();
http.csrf().disable(); //方便开发
http.headers().frameOptions().sameOrigin();
}
/**
* 自定义认证策略
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
// 内存配置, 测试使用
//auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN").and();
auth.authenticationProvider(authProvider()).eraseCredentials(true);
}
@Bean
public AuthProvider authProvider() {
return new AuthProvider();
}
}
然后在浏览器里输入http://localhost:8081/admin/login, 密码admin, admin,可以通过权限认证,说明自定义的数据库权限认证生效了。
3 完善
3.1增加logout
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logout/page")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and();
3.2 不同角色跳转不同登录页面
用户登录跳转用户登录页面
管理员登录跳转到管理员登录页面
- 第一步先编写User对应的UserController
package com.kason.web.controller.user;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Created by IBM on 2018/6/30.
*/
@Controller
public class UserController {
@GetMapping("/user/center")
public String userCenterPage() {
return "user/center";
}
@GetMapping("/user/login")
public String userLoginPage() {
return "user/login";
}
}
对应于admin的AdminController
package com.kason.web.controller.admin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Created by IBM on 2018/6/28.
*/
@Controller
public class AdminController {
@GetMapping("/admin/center")
public String adminCenterPage() {
return "admin/center";
}
@GetMapping("/admin/welcome")
public String welcomePage() {
return "admin/welcome";
}
@GetMapping("/admin/login")
public String adminLoginPage() {
return "/admin/login";
}
}
- 第二步在security包下面编写LoginUrlEntryPoint 类:
package com.kason.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 基于角色的登陆入口控制器
* Created by IBM on 2018/6/30.
*/
public class LoginUrlEntryPoint extends LoginUrlAuthenticationEntryPoint {
private final Map<String, String> authEntryPointMap;
private PathMatcher pathMatcher = new AntPathMatcher(); // 用于进行url 模式匹配用。
public LoginUrlEntryPoint(String loginFormUrl) {
super(loginFormUrl);
authEntryPointMap = new HashMap<>();
// 普通用户登录入口映射, url模式匹配
authEntryPointMap.put("/user/**", "/user/login");
// 管理员登录入口映射, url模式匹配
authEntryPointMap.put("/admin/**", "/admin/login");
}
/**
* 根据请求跳转到指定的页面, 父类是默认使用的loginFormUrl
* @param request
* @param response
* @param exception
* @return
*/
@Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
// 首先获取要跳转的url是什么
String replace = request.getRequestURI().replace(request.getContextPath(), ""); //本地就是去除http://localhost:8081替换成""
// 比如http://localhost:8081/admin/center 将调到http://localhost:8081/admin/login
// http://localhost:8081/user/center 将会跳转到http://localhost:8081/user/login
for(Map.Entry<String, String> entry : this.authEntryPointMap.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), replace)) {
return entry.getValue();
}
}
return super.determineUrlToUseForThisRequest(request, response, exception);
}
}
- 第三步 WebSecurityConfig 中添加登录入口判断
.exceptionHandling()
.authenticationEntryPoint(loginUrlEntryPoint())
.and()
然后WebSecurityConfig就变为了:
package com.kason.config;
import com.kason.security.AuthProvider;
import com.kason.security.LoginUrlEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
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.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
/**
* Created by IBM on 2018/6/30.
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Http权限控制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/login").permitAll() //admin的登陆页面允许所有用户登入
.antMatchers("/user/login").permitAll() //用户的登陆页面允许所有人登入
.antMatchers("/static/**").permitAll() //静态资源允许全部获取权限
.antMatchers("/admin/**").hasRole("ADMIN") //除了/admin/login之外的/admin/**只允许admin权限用户使用
.antMatchers("/user/**").hasAnyRole("ADMIN","USER")
.antMatchers("/api/user/**").hasAnyRole("ADMIN","USER")
.and()
.formLogin()
.loginProcessingUrl("/login") //配置角色登陆处理入口
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logout/page")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.authenticationEntryPoint(loginUrlEntryPoint())
.and();
http.csrf().disable(); //方便开发
http.headers().frameOptions().sameOrigin();
}
/**
* 自定义认证策略
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
// 内存配置, 测试使用
//auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN").and();
auth.authenticationProvider(authProvider()).eraseCredentials(true);
}
@Bean
public AuthProvider authProvider() {
return new AuthProvider();
}
@Bean
public LoginUrlEntryPoint loginUrlEntryPoint() {
return new LoginUrlEntryPoint("/user/login");
}
}
3.3 键入登录失败跳转
首先在security包下编写校验失败处理类:
package com.kason.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登陆失败验证处理
* Created by IBM on 2018/6/30.
*/
public class LoginAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
private final LoginUrlEntryPoint urlEntryPoint;
public LoginAuthFailHandler(LoginUrlEntryPoint urlEntryPoint) {
this.urlEntryPoint = urlEntryPoint;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String targetUrl = this.urlEntryPoint.determineUrlToUseForThisRequest(request, response, exception);
targetUrl += "?" + exception.getMessage();
super.setDefaultFailureUrl(targetUrl);
super.onAuthenticationFailure(request, response, exception);
}
}
然后在WebSecurityConfig login后面加入faliHander()
.loginProcessingUrl("/login") //配置角色登陆处理入口
.failureHandler(loginAuthFailHandler())
loginAuthFailHandler方法就是实例化的bean
@Bean
public LoginUrlEntryPoint loginUrlEntryPoint() {
return new LoginUrlEntryPoint("/user/login");
}
@Bean
public LoginAuthFailHandler loginAuthFailHandler() {
return new LoginAuthFailHandler(loginUrlEntryPoint());
}
网友评论