首先引入security jar 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!---security与thymeleaf整合jar-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
先来个配置类
/**
* created by jasonwang
* on 2019/7/23 14:25
* security安全认证配置
*/
// 不要忘记
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private CustomLoginFailureHandler loginFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 定义当需要用户登录时候,转到的登录页面。
.loginPage("/login") // 设置登录页面
.loginProcessingUrl("/pass/user/login") // 自定义的登录接口
.successForwardUrl("/auth/index") //该请求必须是post请求
.failureHandler(loginFailureHandler)
.and()
.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/login","/pass/**","/webjars/**","/css/**", "/js/**", "/fonts/**", "/images/**")
.permitAll() // 不需要保护的
.anyRequest() // 任何请求,登录后可以访问
.authenticated()
.and()
.csrf().disable(); // 关闭csrf防护 (防护跨域攻击,app服务时一般需要关闭)
// 开启注销功能
http.logout()
.logoutSuccessUrl("/login")
;
}
/**
* 密码加密规则,这是默认的,也可以自己定义
* @return
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//指定自定义的user服务类以及密码加密策略
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**",
"/favicon.ico"
);
}
}
自定义用户信息服务类
@Slf4j 若是红色,一个是要引入lombokjar包,二要在idea中配置,可参照我的另一篇文章:
https://blog.csdn.net/wjs040/article/details/96301593
@Slf4j
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("用户的用户名: {}", username);
//在此处可写自己的逻辑,可与数据库关联,查找该用户的信息,若用户不存在则抛出异常
// TODO 根据用户名,查找到对应的密码,与权限
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123456");
//String password = "123456";
log.info("用户的密码: {}", password);
// 封装用户信息,并返回。参数分别是:用户名,密码,用户权限
User user = new User("wjs040", password,
AuthorityUtils.commaSeparatedStringToAuthorityList("SEARCHUSER,ADDUSER,UPDATEUSER"));
return user;
}
}
自定义验证用户名密码的逻辑
//若使用的话 去掉注释,然后在配置类中的方法中加入:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
//auth.authenticationProvider(new CustomAuthenticationProvider());
}
//@Component
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();
PasswordEncoder passwordEncoder = customAuthenticationProvider.getPasswordEncoder();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
//这个方法一定也要重写哦,就是把里面的注释点即可。不然启动都会报错,
protected void doAfterPropertiesSet() throws Exception {
//Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
}
自定义处理失败管理
@Component
public class CustomLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
public CustomLoginFailureHandler(){
this.setDefaultFailureUrl("/login?error=true");
}
//在这里面可以处理详细的失败信息,和上面的一般不要同时都使用
/* public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
CustomLoginFailureHandler customLoginFailureHandler = new CustomLoginFailureHandler();
String defaultFailureUrl = "/login";
if(exception instanceof UsernameNotFoundException){
defaultFailureUrl += "?error="+ ExceptionCode.USERNAMEERROR;
} else if(exception instanceof BadCredentialsException){
defaultFailureUrl += "?error="+ ExceptionCode.PWDERROR;
} else{
defaultFailureUrl += "?error="+ ExceptionCode.SYSTEMERROR;
}
saveException(request, exception);
logger.debug("Redirecting to " + defaultFailureUrl);
customLoginFailureHandler.getRedirectStrategy().sendRedirect(request, response, defaultFailureUrl);
}*/
}
自定义请求访问验证类,即授权控制
/**
* created by jasonwang
* on 2019/7/24 10:09
* 注册自定义权限验证规则,本类不需要在CustomSecurityConfig里面被@Bean,已经帮我们自动做了,
* 如果再加入的话,就会报错。下面这段看看就行了,不用加入使用。
* @Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(customPermissionEvaluator);
return handler;
}*/
@Component
public class CustomPermissionEvaluator extends DenyAllPermissionEvaluator {
@Autowired
private SysPermissionService permissionService;
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
// 获得loadUserByUsername()方法的结果
User user = (User)authentication.getPrincipal();
// 获得loadUserByUsername()中注入的角色
Collection<GrantedAuthority> authorities = user.getAuthorities();
// 遍历用户所有角色
for(GrantedAuthority authority : authorities) {
String roleName = authority.getAuthority();
//简易版的验证,GrantedAuthority这里面存放的是权限
if(roleName.equals(permission)) return true;
/*Integer roleId = roleService.selectByName(roleName).getId();
// 得到角色所有的权限
List<SysPermission> permissionList = permissionService.listByRoleId(roleId);*/
//此处先写出固定的,用于搭建框架测试 begin
/*List<SysPermission> permissionList = new ArrayList<>();
SysPermission sysPermission2 = new SysPermission();
sysPermission2.setPermission("SEARCHUSER,ADDUSER,UPDATEUSER");
permissionList.add(sysPermission2);
// end
// 遍历permissionList
for(SysPermission sysPermission : permissionList) {
// 获取权限集
List permissions = sysPermission.getPermissions();
// 如果访问的Url和权限用户符合的话,返回true
if(targetUrl.equals(sysPermission.getUrl())
&& permissions.contains(permission)) {
return true;
}
}*/
}
return false;
}
}
//controller参考例子
@GetMapping("/auth/search/user")
@PreAuthorize("hasPermission('/user','SEARCHUSER')")
public String searchUser(ModelMap modelMap) throws CustomException {
SysUser sysUser = new SysUser();
sysUser.setUserCode("1001");
APPContext.setLoginInfo(sysUser);
log.info("/auth/search/user");
modelMap.put("user","/auth/search/user");
return "index";
}
/**
* 权限实体类
* created by jasonwang
* on 2019/7/24 10:14
* @Data、@NoArgsConstructor、@AllArgsConstructor这些注解,都是lombok帮助处理set/get和全参无参构造方法的
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysPermission implements Serializable {
static final long serialVersionUID = 1L;
private Integer id;
private String url;
private Integer roleId;
private String permission;
private List permissions;
public List getPermissions() {
return Arrays.asList(this.permission.trim().split(","));
}
public void setPermissions(List permissions) {
this.permissions = permissions;
}
}
下面附带上controller以及页面
@GetMapping("/login")
public String login() throws CustomException {
log.info("进入登录页");
return "login";
}
/**
* security成功后执行的url,必须是post的请求
* @return
*/
@PostMapping("/auth/index")
public String index(){
System.out.println("进入首页:index");
return "index";
}
/**
* 权限控制例子
* @param modelMap
* @return
* @throws CustomException
*/
@GetMapping("/auth/search/user")
@PreAuthorize("hasPermission('/user','SEARCHUSER')")
public String searchUser(ModelMap modelMap) throws CustomException {
SysUser sysUser = new SysUser();
sysUser.setUserCode("1001");
APPContext.setLoginInfo(sysUser);
log.info("/auth/search/user");
modelMap.put("user","/auth/search/user");
return "index";
}
/**
* 不是跳转页面,必须加@ResponseBody
* @return
* @throws CustomException
*/
@GetMapping("/getMytest")
@SystemControllerLog(descrption = "查询用户信息",actionType = "4")
@ResponseBody
public ResultBean<Mytest> getMytestInfo() throws CustomException {
return ResultBean.success( mytestService.selectAll());
}
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.4.1/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/3.4.1/css/bootstrap.min.css}">
<script src="/webjars/jquery/1.12.4/jquery.min.js" th:src="@{/webjars/jquery/1.12.4/jquery.min.js}"></script>
<script src="/webjars/bootstrap/3.4.1/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/3.4.1/js/bootstrap.min.js}"></script>
</head>
<body>
<form action="/pass/user/login" method="post">
<h2>自定义登录页面</h2>
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" name="username" class="form-control" id="username" placeholder="username">
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" name="password" class="form-control" id="password" placeholder="password">
</div>
<span style="color: red;" th:text="用户名或密码错误" th:if="${param.error}"></span>
<button type="submit" class="btn btn-default">登录</button>
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.4.1/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/3.4.1/css/bootstrap.min.css}">
<script src="/webjars/jquery/1.12.4/jquery.min.js" th:src="@{/webjars/jquery/1.12.4/jquery.min.js}"></script>
<script src="/webjars/bootstrap/3.4.1/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/3.4.1/js/bootstrap.min.js}"></script>
</head>
<body>
index
<h1>登录才能查看 [[${user}]]</h1>
<br>
<br>
<!-- 判断是否登录 登录显示 div 的内容,未登录不显示 -->
<div sec:authorize="isAuthenticated()">判断是否登录</div><hr>
<!-- 输出账号名称 -->
<span sec:authentication="name"></span><hr>
<!-- 输出拥有的角色 -->
<span sec:authentication="principal.authorities"></span><hr>
<!-- 判断是否有该角色,有就显示,没有不显示 -->
<div sec:authorize="hasRole('SEARCHUSER')">判断是否有该角色("SEARCHUSER")</div><hr>
<div sec:authorize="hasRole('ROLE_USER')">判断是否有该角色("ROLE_USER")</div><hr>
<button onclick="test()">测试</button>
<script>
function test() {
$.get('/getMytest',function (data) {
alert(JSON.stringify(data));
});
}
</script>
</body>
</html>